一、定义
循环链表是另一种形式的链式存储结构。它的特点是表中最后一个节点的指针域指向头结点,整个链表形成一个环
二、循环链表的数据结构
循环链表的数据结构和单链表相同,分为数据域和指针域,指针域指向当前节点的下一节点,代码如下:
typedef struct Node
{
int data;
struct Node *next;
}Node,*LinkList;
三、循环链表的相关操作
在进行循环链表相关操作前必须先清楚,在操作循环链表过程中,什么时候使用一级指针,什么时候使用二级指针:需要从头结点开始进行的操作需要以二级指针为参数,否则用一级指着即可。
用二级指针来代表整个链表,在遍历、插入等需要从头结点开始的操作中,参数用能代表整个链表的二级指针,只需要进行一次解引用即可得到头结点的地址。
可以类比二维数组来理解,假设存在int arr[3][4] 和 Node **p(由上文可知Node **p 和 LinkList *p中p意义相同)
无解引用 | 一次解引用 | 两次解引用 | |
二维数组 | arr | *arr | **arr |
整个数组首地址 | 第一行地址 | 第一个元素值 | |
循环链表 | *p | *p | **p或(*p)-> |
整个链表首地址 | 头节点地址 | 头节点指针域(注意4) |
注意:
1、二维数组arr并不是二级指针,这一点并不同于p
2、*和->都有解引用的意思
3、头结点一般没有值,因此只有头结点的指针域有意义
1、初始化
bool InitList(LinkList *List)
{
/*通过对代表整个个链表的二级指针List进行一次解引用,进入代表的头节点的(*List),可以改变代表头节点的指向(即指向申请的内存空间)*/
*List = (LinkList)malloc(sizeof(Node));
if((*List) == NULL)
{
printf("申请内存失败\n");
exit(0);
}
/*通过一次解引用进入头节点,再通过一次解引用(->)改变头节点的值(指针域)*/
(*List)->next = *List;
return true;
}
2、判断是否为空
判断链表是否为空并不需要修改指针,因此参数只需要给出某一节点的地址,表示从该节点开始判断,如果该节点的指针域指向自己,即为空
void ListEmpty(LinkList List)
{
if(List->next == List)//该节点的指针域指向自己
{
printf("循环链表为空!\n");
}
else
{
printf("循环链表为非空!\n");
}
}
3、插入
在第i个节点前插入元素e,在插入前需要先找到第i-1个节点,因此需要设置计数器,从头节点开始遍历,知道第i-1个节点,然后将建立新的节点插在第i-1个节点后面。
因为要从头结点开始计数,因此需要知道头结点地址,故参数为二级指针L,对L一次解引用即可得到头结点的地址
bool ListInsert(LinkList *L, int i, int e)
{
//在第i个元素前插入元素e
LinkList p = (*L)->next; //p指向头结点
LinkList s;
int j = 0;
if(i <= 0 || i > ListLength(*L) + 1)//i值不合法
{
return false;
}
while(j < i-1)//寻找第i个节点
{
p = p->next;
j++;
}
s = (LinkList)malloc(sizeof(Node));
s->data = e;
s->next = p->next;
p->next = s;
if(p == *L)//头结点改变
{
*L = s;
}
return true;
}
4、求表长
求表长并不需要从头结点开始,从任意节点都可以,从某一节点遍历,直到便利到某一节点的指针域指向了该节点,即到达表尾,在遍历过程中设置计数器记录结点个数即可
int ListLength(LinkList L)
{
int i = 0;
LinkList p = L->next;
while(p != L)//没到表尾
{
i++;
p = p->next;
}
return i;
}
5、查找
查找是指找出从头开始第i个节点中的值,需从头结点开始,使用二级指针作为参数,首先判断i是否大于该表的长度或小于等于0,i有意义时:从该节点节点开始遍历查找第i个节点,找到后赋值给*e,
bool GetElem(LinkList *L,int i,int *e)
{
//当第i个元素存在时,赋值给e
int j = 1;//计数器
LinkList p = (*L)->next;//p指向第一个节点
if(i <= 0 || i > ListLength(*L))
{
return false;
}
while(j < i)
{
//顺指针向后查找,知道第i个元素
p = p->next;
j++;
}
*e = p->data;
return true;
}
6、遍历输出
遍历需要从头结点开始,因此使用二级指针
bool ListTraverse(LinkList *L)
{
LinkList p = (*L)->next;//p指向头结点后第一个节点
while(p != *L)//当p不等于头节点时循环输出
{
printf("%d ",p->data);
p = p->next;
}
printf("\n");
return true;
}
7、求元素前驱
求元素前驱,即为找出某个元素所在节点的前一个节点中元素值,定义两个节点牌,q,p,p始终指向q,因此在遍历查找过程中,只要q的元素值为要查找的值,那么p中的元素值即为所求。
bool PriorElem(LinkList *L,int cur_e,int *pre_e)
{
LinkList q;
LinkList p = (*L)->next;//p指向第一个节点
q = p->next;//p指向q
while(q != *L)
{
if(q->data == cur_e)
{
*pre_e = p->data;
return true;
}
p = q;
q = q->next;
}
return false;
}
8、求元素后继
求元素后继只需从头结点开始遍历,直到便利到某一节点的值为所求值,则该节点的后继(即该节点指针域所指的节点)即为所求
bool NextElem(LinkList *L,int cur_e,int *next_e)
{
LinkList p = (*L)->next;//p指向第一个节点
while(p != *L)//p没有到表尾
{
if(p->data == cur_e)
{
*next_e = p->next->data;
return true;
}
p = p->next;
}
return false;
}
9、删除
删除第i个节点。
1、首先判断i是否有意义。
2、利用指针p进行遍历,找到第i-1个节点
3、指针q指向第i个节点(q = p -> next)
4、删除第i个节点(p = q -> next)
bool ListDelete(LinkList *L,int i,int *e)
{
LinkList p = (*L)->next;//p指向头结点
LinkList q;
int j = 0;
if(i <= 0 || i > ListLength(*L))
{
return false;
}
while(j < i - 1)
{
p = p->next;
j++;
}
q = p->next;//q指向待删除的节点
p->next = q->next;
*e = q->data;
if(*L == q)
{
*L = p;
}
free(q);
return true;
}
10、清空链表
从头结点开始,free掉除头结点外每一个节点,最后头结点指向自己
bool ClearList(LinkList *L)
{
//将L置为空表
LinkList p,q;
*p = (*L)->next;//指向头结点
while(p != *L)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = *L;//头结点指针域指向自身
return true;
}
11、销毁链表
从头结点开始向后遍历直到表尾,free掉每一节点,最后free掉头结点
bool DestoryList(LinkList *L)
{
//销毁链表
LinkList q,p = (*L)->next;
while(p != *L)
{
q = p->next;
free(p);
p = q;
}
free(*L);
*L = NULL;
return true;
}