数算---线性表(二)
前言
上篇文章数算---线性表(一)我们介绍了线性表中的顺序表和单向链表,这篇文章我们继续将剩余的几种链表一一做个介绍及实现。(代码语言为C语言)
单向循环链表
单向循环链表与单向链表不同的就是尾结点的next指针指向头结点
(如果有头结点,否则就是首元结点),而不是指向NULL
。这篇文章中单向循环链表我们不用添加头结点,即第一个位置为首元结点(如下图非空表)。
准备工作
准备工作代码和上篇单向链表一样。
#include <stdio.h>
#include "stdlib.h"
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
//定义结点
typedef struct Node{
ElemType data;
struct Node *next;
}Node;
typedef struct Node * LinkList;
初始化
我们采用Xcode调试框输入结点数据生成结点的方式来构建一个循环链表。此处没有创建头结点。
Status CreateList(LinkList *L){
int item;
LinkList temp = NULL;
LinkList r = NULL;
printf("请输入新的结点数据,输入0即结束\n");
while (1) {
scanf("%d",&item);
if(item == 0){
break;
}
if(*L == NULL){
*L = (LinkList)malloc(sizeof(Node));
if(!*L) return ERROR;
(*L)->data = item;
(*L)->next = *L;//首元结点指向自己,形成循环链表
r = *L;
}else{
temp = (LinkList)malloc(sizeof(Node));
if(!temp) return ERROR;
temp->data = item;
temp->next = *L;//尾结点next指向首元结点
r->next = temp;//此时temp为尾结点
r = temp;//方便下个数据进来用
}
}
return OK;
}
插入数据
循环链表的数据插入我们需要注意的是插入的位置,如果插入位置是第一个,即插入首元结点,以及插入位置为链表中其他位置,我们分这两种情况分析。
插入首元结点
插入其他位置
Status ListInsert(LinkList *L, int place, int num){
LinkList temp, target;
int i;
if(place == 1){
//创建新结点(要插入的结点)
temp = (LinkList)malloc(sizeof(Node));
if(temp == NULL){
return ERROR;
}
temp->data = num;
//拿到尾结点
for (target = *L; target->next != *L; target = target->next);
//将原来首元结点给到新结点后继
temp->next = *L;
//尾结点的后继为新结点,即第一个结点
target->next = temp;
//将首指针指向新结点
*L = temp;
}else{
temp = (LinkList)malloc(sizeof(Node));
if(temp == NULL){
return ERROR;
}
temp->data = num;
//找到插入位置前一个结点
for (i = 1, target = *L;target->next != *L && i != place - 1; target = target->next,i ++);
temp->next = target->next;
target->next = temp;
}
return OK;
}
遍历数据
循环链表适合用do-while循环。
void show(LinkList p){
if(p == NULL){
printf("链表为空\n");
return;
}else{
LinkList temp;
temp = p;
do {
printf("%5d",temp->data);
temp = temp->next;
} while (temp != p);
printf("\n");
}
}
删除数据
删除结点需要注意的是所删除位置是第一个还是其他位置,删除位置为1是还需要判断是否当前链表只有一个结点的情况,删除后链表就为空表。
Status LinkListDelete(LinkList *L, int place){
LinkList temp,target;
int i;
temp = *L;
if(temp == NULL) return ERROR;
if(place == 1){
if((*L)->next == (*L)){
(*L) = NULL;
return OK;
}
//找到尾结点
for (target = *L; target->next != *L; target = target->next);
temp = *L;
//将原来第二个结点作为首元结点
*L = (*L)->next;
//重新将尾结点指向首元结点
target->next = *L;
free(temp);
}else{
//找到要删除的前一个结点
for (i = 1,target = *L; target->next !=*L && i != place - 1; target = target->next,i++);
temp = target->next;
//将删除结点的后继给到前一个的后继
target->next = temp->next;
free(temp);
}
return OK;
}
查找数据
查找数据在链表中的位置,需要注意的是循环遍历链表的循环跳出条件,要考虑到链表中找不到的情况。
int findValue(LinkList L, int value){
int i = 1;
LinkList p;
p = L;
//找到 data == value 的结点
while (p->data != value && p->next != L) {
i ++;
p = p->next;
}
//此时 可能 第i个结点p即为要查找的结点,但需要注意p->next != L 也会跳出循环
//所以需要排除尾结点不满足的情况,也就是此链表没匹配到要找的数据
if(p->next == L && p->data != value){
return -1;
}
return i;
}
双向链表
不同于单向链表结构,双向链表的结点多个一个指向前驱结点的指针域。这样从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
双向链表结点
创建链表
准备代码与上方有所差别,结点的结构需要增加一个指针域。
typedef struct Node{
ElemType data;
struct Node *prior;
struct Node *next;
}Node;
//创建链表
Status CreateLinkList(LinkList *L){
*L = (LinkList)malloc(sizeof(Node));
if(*L == NULL) return ERROR;
//头结点
(*L)->prior = NULL;
(*L)->next = NULL;
(*L)->data = -1;
LinkList p = *L;
for (int i = 0; i < 10; i ++) {
LinkList temp = (LinkList)malloc(sizeof(Node));
temp->prior = NULL;
temp->next = NULL;
temp->data = i;
p->next = temp;
temp->prior = p;
p = p->next;
}
return OK;
}
插入数据
双向链表插入数据和单向链表插入的原理是一样的,就是需要判断是否插入链尾和前驱指向的问题。注意点就是先将新结点后驱指向后面的结点,先建立与后面结点的关系,再来处理与前驱结点的关系。
双向链表插入
Status ListInsert(LinkList *L, int i , ElemType data){
if(i < 1) return ERROR;
LinkList temp = (LinkList)malloc(sizeof(Node));
temp->prior = NULL;
temp->next = NULL;
temp->data = data;
LinkList p = *L;//带有头结点的
//找到第i-1个位置的结点
for (int j = 1; j < i && p; j ++) {
p = p->next;
}
//i 3
//j 1 2 3
//p 1 2
//此时p为i-1 处的结点
//插入位置大于链表的长度
if(p == NULL){
return ERROR;
}
//插入到尾部,直接接上去就可以
if(p->next == NULL){
p->next = temp;
temp->prior = p;
}else{
//插入到位置为非尾部
p->next->prior = temp;
temp->next = p->next;
p->next = temp;
temp->prior = p;
}
return OK;
}
删除数据
删除数据就是插入的逆向操作,需要先找到删除位置的前一个结点,然后将它与删除位置后一个结点建立联系,最后需要释放所删除结点的内存。
双向链表删除
Status ListDelete(LinkList *L, int i, ElemType *e){
int k = 1;
LinkList p = (*L);
if(*L == NULL){
return ERROR;
}
//找到第i-1个结点
while (k < i && p != NULL) {
p = p->next;
k ++;
}
if(k > i || p == NULL){
return ERROR;
}
LinkList delTemp = p->next;
*e = delTemp->data;
//将要删除的结点后驱给到前一个结点的后驱,一前一后建立上联系
p->next = delTemp->next;
//判断是否删除的是尾结点,不是的话需要设置后一个结点的前驱为前一个结点
if(delTemp->next != NULL){
delTemp->next->prior = p;
}
free(delTemp);
return OK;
}
双向循环链表
双向循环链表就是在双向链表基础上,将表尾和表头建立关系。表尾next指向表头,表头prior指向表尾,形成循环结构。所以循环链表的尾结点next是不会为NULL的。
双向循环链表
在创建链表、插入结点、删除结点等操作时,要考虑到需要建立头结点和尾结点之间的关系。
创建链表
注意处理尾结点和头结点的关系。
Status createLinkList(LinkList *L){
*L = (LinkList)malloc(sizeof(Node));
if(*L == NULL){
return ERROR;
}
(*L)->next = (*L);
(*L)->prior = (*L);
LinkList p = *L;
for (int i = 0; i < 10; i ++) {
LinkList temp = (LinkList)malloc(sizeof(Node));
temp->data = i;
//temp 接在头结点后
p->next = temp;
//前驱结点为p
temp->prior = p;
//后驱结点为头结点
temp->next = (*L);
//头结点的前驱结点为temp
(*L)->prior = temp;
//方便后续接上
p = p->next;
}
return OK;
}
插入数据
与双向链表不同的是需要处理首尾联系。
Status LinkListInsert(LinkList *L,int index, ElemType e){
LinkList p = (*L);
int i = 1;
if (*L == NULL) return ERROR;
//找到插入位置前一个
while (i < index && p->next != *L) {
p = p->next;
i++;
}
if(i > index) return ERROR;
LinkList temp = (LinkList)malloc(sizeof(Node));
if (temp == NULL) return ERROR;
temp->data = e;
temp->prior = p;
temp->next = p->next;
p->next = temp;
if(*L != temp->next){
//如果插入的不是尾结点,建立后驱结点的前驱关系
temp->next->prior = temp;
}else{
//插入的是尾结点,就建立与头结点的关系
(*L)->prior = temp;
}
return OK;
}
删除数据
由于头结点和尾结点的关系,删除结点时不用考虑尾结点的特殊情况,只需要注意删除当前结点后只剩下头结点的情况,需要置空。
Status LinListDelete(LinkList *L,int index,ElemType *e){
int i = 1;
LinkList temp = (*L)->next;
if(*L == NULL){
return ERROR;
}
//即此时删除的是首元结点,删了之后就直接*L置空
if(temp->next == *L){
free(*L);
(*L) = NULL;
return OK;
}
while (i < index) {
temp = temp->next;
i++;
}
*e = temp->data;
//将前一个和后一个关联上
temp->prior->next = temp->next;
temp->next->prior = temp->prior;
free(temp);
return OK;
}