循环链表是头尾相接的链表。循环链表的最后一个节点的指针
域指向链表的头结点或首元结点(没有头结点的情况下)。
下图是有头结点的单向循环链表:
双向循环链表:
通过以下内容来学习单向循环链表的使用:
1. 单向循环列表的创建-尾插法
2. 单向循环列表的插入
3. 单向循环列表的删除
4. 单向循环列表的查询
注意!!本文中代码使用没有头结点的单向循环列表演示,第一个结点是列表的首元结点。
- 两种创建方式的区别:使用头结点的单向循环链表,链表指针指向头结点不会改变,在插入和删除结点的操作中,不用移动链表的指针。而不使用头结点则需要根据结点位置来判断是否需要移动链表的指针,较为复杂。
- 定义单向循环链表的结构体结点:
#define ERROR 0
#define OK 1
#define TRUE 1
#define FALSE 0
typedef int Status;//状态值
typedef int ElemType;//结点的数据类型,视实际情况而定。在此以int为例。
//定义结点
typedef struct Node{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;
1. 单向循环列表的创建
以尾插法创建单向循环链表有以下几步:
① 判断链表为空则创建链表;
② 为首元结点赋值,并将指针域指向本身;
③ 不为空则创建新结点,并找到当前列表的尾结点,将新结点设置为尾结点。
代码如下:
//创建单向循环列表
Status CreateList(LinkList *L) {
ElemType item;
LinkList temp,tail;
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;
//标记尾结点
tail = *L;
}else {
temp = (LinkList)malloc(sizeof(Node));
//健壮性
if (!temp) return ERROR;
temp->data = item;
temp->next = *L;
tail->next = temp;
tail = temp;
}
}
return OK;
}
以上代码,使用临时变量tail
记录尾结点,也可以遍历查找尾结点。但是从时间复杂度分析,该算法的时间复杂度为O(1)
,如果使用循环查找,时间复杂度为O(n)
耗费使用临时变量更好。
2. 单向循环列表的插入
插入结点分为两种不同的情况,一种是在首元结点的位置插入新结点,另一种是在其他位置插入新结点。
- 在首元结点位置插入新结点的步骤:
① 创建新结点,指针域指向首元结点;
② 找到尾结点,尾结点指针域指向新结点;
③ 链表头指针指向新结点。 - 在其他位置插入新结点的步骤:
① 找到插入位置的前一个结点target
;
② 创建一个新的结点,指针域指向target
的下一个结点;
③target
的指针域指向新结点。
代码如下:
Status InsertNode(LinkList *L) {
int place;
ElemType item;
LinkList temp,target;
printf("请输入插入新结点的位置:\n");
scanf("%d",&place);
if (place < 1) {
printf("请输入大于0的位置:\n");
scanf("%d",&place);
}
printf("请输入插入新结点的值:\n");
scanf("%d",&item);
//在首元结点位置插入,设这里链表结点位置从1开始
if (place == 1) {
/*插如到首元结点需要做的事
1.创建新结点,指针域指向首元结点;
2.找到尾结点,尾结点指针域指向新结点;
3.链表头指针指向新结点。
*/
temp = (LinkList)malloc(sizeof(Node));
if (temp == NULL) return ERROR;
temp->data = item;
temp->next = *L;
//寻找尾结点tail
for (target = *L; target->next != *L; target = target->next);
target->next = temp;
*L = temp;
}else {//在其他结点位置插入
/*插入到其他位置需要做的事:
1.找到插入位置的前一个结点target;
2.创建一个新的结点,指针域指向target的下一个结点;
3.`target`的指针域指向新结点。
*/
//循环,找到插入位置place的前一个结点
target = *L;
int I;
for (i = 1; target->next != *L && i != place-1; i++) {
target = target->next;
}
//判断place
if (place > i && target->next == *L) {
printf("输入的位置太大了\n");
return ERROR;
}
temp = (LinkList)malloc(sizeof(Node));
temp->data = item;
temp->next = target->next;
target->next = temp;
}
return OK;
}
3. 单向循环列表的删除
删除结点分为两种不同的情况,一种是在删除首元结点,另一种是删除其他位置的结点。
- 删除首元结点需要做的事情:
①找到尾结点,尾结点的指针域指向首元结点的下一个结点;
②链表指针指向首元结点的下一个结点;
③释放原来的首元结点。 - 删除其他结点需要做的事情:
①找到删除位置的前一个结点target
;
②保存删除位置的结点;
③target
的指针域指向删除位置的下一个结点;
④释放删除位置的结点。
代码如下:
Status DeletNode(LinkList *L){
int place;
LinkList temp,target = NULL;
printf("请输入删除结点的位置:\n");
scanf("%d",&place);
if (place < 1) {
printf("请输入大于0的位置:\n");
scanf("%d",&place);
}
if (place == 1) {//删除首元结点,位置从1开始
/*删除首元结点需要做的事情:
①找到尾结点,尾结点的指针域指向首元结点的下一个结点;
②链表指针指向首元结点的下一个结点;
③释放原来的首元结点。
*/
//判断当链表中只有1个结点时,直接链表指针
if((*L)->next == (*L)){
(*L) = NULL;
return OK;
}
//记录首元结点
temp = *L;
for (target = *L; target->next != *L; target = target->next) ;
*L = (*L)->next;
target->next = *L;
free(temp);
}else {//删除其他结点
/*删除其他结点需要做的事情:
①找到删除位置的前一个结点target;
②保存删除位置的结点;
③target的指针域指向删除位置的下一个结点;
④释放删除位置的结点。
*/
int I;
target = *L;
for (i = 1; target->next != *L && i != place-1; i++) {
target = target->next;
}
if (place > i && target->next == *L) {
printf("输入的位置太大了\n");
return ERROR;
}
temp = target->next;
target->next = temp->next;
free(temp);
}
return OK;
}
4. 单向循环列表的查询
循环查找单向循环链表L
中值为value
的结点:
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;
}
//当尾结点指向头结点就会直接跳出循环,所以要额外增加一次判断尾结点的data
if (p->next == L && p->data != value) {
return -1;//返回没有找到
}
return i;//返回找到的位置
}