单向循环链表的初体验
单向循链表
单向循环链表经过小白今天一天的学习,感觉就是在单向链表的尾结点加了一个首结点的地址,不再指向NULL,个人理解就像一群孩子围成了一个圆,头尾相连,同时多少个孩子就是多少个结点,如击鼓传花一般一个个将手上的手绢传递给下一个人,一遍下来就像是单向循环的遍历,想要到谁的手上,就像是指定位置进行对链表的修改,删除呢就是某个人在游戏中被淘汰了,可以是头部,也可以是尾部,同时也可以是中间的某个人。由此的启发。
经过再次查看并测试,发现了仍有错误的地方,在遍历的代码中判断链表是否为空,为空时头结点指向本身,这里的判断出错了,我写的指向NULL,还有头删中在链表只有一个首结点的情况下进行处理,但没有退出,导致代码继续运行访问了保留区,发生段错误。
希望看完代码的你发现错误,请评论指正,非常感谢。
示意图如下:

新链表的初始化
创建结构体存储结点数据
-
/***************************************************************** * * file name :one_way_circularLinkediIst * authour :yq_dyx@163.com * date :2024/04/26 * function :设计一个函数,实现对单向循环链表的增删改查的功能。 * note :None * CopyRight (c) 2024 yq_dyx@163.com All Right Reseverd * ******************************************************************/ #include <stdio.h> #include <stdlib.h> #include <stdbool.h> typedef int DataType_t; //定义一个链表结构体,分别存储数据域和指针域 typedef struct CircularLinkedList { DataType_t data; // 数据域 struct CircularLinkedList *next; // 指针域 }CircLList_t;创建空链表
创建一个空的链表和头结点,并进行初始化
//创建空链表以及头结点 /******************************************************************* * * name : CircLList_crea * function : 创建一个空链表并创建一个头结点,并对头结点进行初始化 * argument : * retval : None * author : yq_dyx@163.com * date : 2024/04/26 * note : None * * *****************************************************************/ CircLList_t * CircLList_creat() { CircLList_t *head = (CircLList_t *)malloc(sizeof(CircLList_t)); //创建头结点 if(NULL == head) { perror("calloc head is error\n"); // 分配内存失败 exit(1); } head->next = head; //头结点初始化,为表示循环所以指向自己 }创建新结点
在添加结点时候必然要创建新的结点,再进行添加,代码如下:
//创建新节点
/*******************************************************************
*
* name : CircLList_NewNode
* function : 创建一新的链表节点,并对节点初始化
* argument : @data 新结点的数据域的数据
* retval : None
* author : yq_dyx@163.com
* date : 2024/04/23
* note : 此函数不单独使用,需要配合Insert函数使用
*
* *****************************************************************/
CircLList_t * CircLList_NewNode(DataType_t data)
{
CircLList_t *New =(CircLList_t *)malloc(sizeof(CircLList_t));
if(NULL == New)
{
perror("calloc NewNode is error\n"); // 分配内存失败
return NULL;
}
New->data = data; //新结点数据域初始化
New->next = NULL; //新结点指针域初始化
}
插入操作
头插
当在头部进行插入新结点动作的时候,可以先画出下图,方便更好的理解

-
//头部插入 /******************************************************************* * * name : CircLList_HeadInsert * function : 节点在头部插入 * argument : @data 要插入的结点的数据域的数据 * @head 链表的头结点 * retval : None * author : yq_dyx@163.com * date : 2024/04/23 * note : 要判断此链表是否为空,为空则直接在头结点后面插入,且此函数需要配合creat函数使用 * * *****************************************************************/ bool CircLList_HeadInsert(CircLList_t *head,DataType_t data) { CircLList_t *phead = head; //备份头文件,用作遍历寻找尾结点 CircLList_t *New = CircLList_NewNode(data); // 创建新节点, 并初始化 if(NULL == New) //判断新节点是否创建成功 { printf("calloc NewNode is failed\n"); return false; } if(head == head->next) //判断链表是否为空 { head->next = New; New->next = New; return true; } while(phead->next) { phead = phead->next; if(head->next == phead->next) //寻找尾结点,尾结点指向首结点 break; } New->next = head->next; head->next = New; phead->next = New; return true; }
尾插
当选择在尾部插入的时候,我们先画出下图,这样更有利于我们理解

/*******************************************************************
*
* name : CircLList_TailInsert
* function : 节点在尾部插入
* argument : @data 要插入的结点的数据域的数据
* @head 链表的头结点
* retval : None
* author : yq_dyx@163.com
* date : 2024/04/23
* note : 要判断此链表是否为空,为空则直接在头结点后面插入,需要循环找到尾结点,尾结点指向首结点
*
* *****************************************************************/
bool CircLList_TailInsert(CircLList_t *head,DataType_t data)
{
CircLList_t *phead = head; // 备份头结点
CircLList_t *New = CircLList_NewNode(data);
if(NULL == New) //判断新节点是否创建成功
{
printf("calloc NewNode is failed\n");
return false;
}
if(head == head->next) //判断链表是否为空,为空就加在头结点后
{
head->next = New;
New->next = New;
return true;
}
while(phead->next)
{
phead = phead->next;
if(head->next == phead->next) //寻找尾结点,尾结点指向首结点
break;
}
phead->next = New;
New->next = head->next;
return true;
}
中间插入
当进行中间插入时需要考虑多种情况,比如链表为空,或者找不到数据,我们都需要考虑,也画了下图进行简单分析有利于理解

-
/******************************************************************* * * name : CircLList_DestInsert * function : 结点在指定结点后插入 * argument : @data 指定结点的数据域的数据 * @head 链表的头结点 * @value 要插入的结点的数据域的数据 * retval : None * author : yq_dyx@163.com * date : 2024/04/23 * note : 要判断此链表是否为空,为空则直接在头结点后面插入 * * *****************************************************************/ bool CircLList_DestInsert(CircLList_t *head,DataType_t data,DataType_t value) { CircLList_t *phead = head->next; // 备份头结点 CircLList_t *New = CircLList_NewNode(value); if(NULL == New) //判断新节点是否创建成功 { printf("calloc NewNode is failed\n"); return false; } if(head == head->next) //判断链表是否为空,为空就加在头结点后 { head->next = New; New->next = New; return true; } while(phead->next && phead->data != data) //再循环再次到首结点时退出,说明没有该数据 { if(phead->next == head->next) { printf("not found this data\n"); return false; } phead = phead->next; // 遍历链表,phead的地址改变,到下一个结点 } New->next = phead->next; //让尾结点把头结点的地址给新节点 phead->next = New; //连接尾结点和新结点 return true; }
删除操作
删除首节点
对于删除操作咱们先进行头部删除,尾部删除和中间指定删除

-
/******************************************************************* * * name : CircLList_HeadDelete * function : 删除头部的结点 * argument : @head 链表的头结点 * retval : None * author : yq_dyx@163.com * date : 2024/04/23 * note : 要判断此链表是否为空,为空则直接退出,要找到尾结点,把新首结点的地址给尾结点 * * *****************************************************************/ bool CircLList_HeadDelete(CircLList_t *head) { CircLList_t *phead = head; // 备份头结点,用于循环遍历 CircLList_t *p = head->next; // 循环中用于备份首结点 if(NULL == head->next) //判断链表是否为空,为空直接退出 { printf("linkedlist is empty\n"); return false; } if(p->next == head->next) //当链表中只有一个首结点 { p = NULL; head->next = head; free(p); return true; } while(phead->next) { phead = phead->next; if(head->next == phead->next) //寻找尾结点,尾结点指向首结点 break; } head->next = p->next; phead->next = p->next; //对删除的结点释放,防止内存泄漏,以及段错误 p->next = NULL; free(p); return true; }
删除尾节点
删除尾结点较为简单,操作不复杂,主要就是断开就好,就像下图所示:

/*******************************************************************
*
* name : CircLList_TailDelete
* function : 删除尾部的结点
* argument : @head 链表的头结点
* retval : None
* author : yq_dyx@163.com
* date : 2024/04/23
* note : 要判断此链表是否为空,为空则直接退出,要找到尾结点,把首结点的地址给新的尾结点
*
* *****************************************************************/
bool CircLList_TailDelete(CircLList_t *head)
{
CircLList_t *phead = head; // 备份头结点,用于循环遍历
CircLList_t *p = head->next; // 循环中用于备份phead
if(NULL == head->next) //判断链表是否为空,为空直接退出
{
printf("linkedlist is empty\n");
return false;
}
if(p->next == head->next) //当链表中只有一个首结点
{
p = NULL;
head->next = head;
free(p);
}
while(phead->next)
{
p = phead; //记录phead改变前的地址也就是当前结点
phead = phead->next; // 遍历链表,phead的地址改变,到下一个结点
if(head->next == phead->next) //寻找尾结点,尾结点指向首结点
break;
}
p->next = head->next; //将新的尾结点连接首结点
phead->next = NULL;
free(phead); //对删除的结点释放,防止内存泄漏,以及段错误
return true;
}
中间删除指定结点
找到指定结点,但需要考虑多种情况,比如链表就一个首结点,链表为空,指定结点为首结点或者尾结点,简单图示如下:

-
/******************************************************************* * * name : doubleLinkedList_DestDelete * function : 删除指定位置的结点 * argument : @data 指定结点的数据域的数据 * @head 链表的头结点 * retval : None * author : yq_dyx@163.com * date : 2024/04/23 * note : None * * *****************************************************************/ bool doubleLinkedList_DestDelete(doubLList_t *head,DataType_t data) { if(NULL == head->next) //判断链表是否为空,为空直接退出 { printf("doubLinkedList is empty\n"); return false; } doubLList_t *phead = head->next; // 备份头结点 doubLList_t *p = head; // 备份头结点 while(phead->next && phead->data != data) //再循环再次到尾巴结点时退出,说明没有该数据 { phead = phead->next; // 遍历链表,找到和data相同的节点时(phead)退出, } if(phead->next == NULL && phead->data != data) //当phead == NULL导致退出时,又没找该data,函数直接退出 { printf("not found this data\n"); return false; } //头部删除 if(phead == head->next) //当目标结点为首结点 {
打印操作
对于整个链表进行遍历,当然也有不同情况要考虑,链表为空等等,切尾结点也需要打印
-
/******************************************************************* * * name : CircLList_Print * function : 遍历所有的结点,并打印结点的数据域 * argument : @head 链表的头结点 * retval : None * author : yq_dyx@163.com * date : 2024/04/23 * note : 要判断此链表是否为空,为空则直接退出,要找到尾结点,同时要把尾结点的数据域打印 * * *****************************************************************/ bool CircLList_Print(CircLList_t*head) { CircLList_t *phead = head; // 备份头结点 if(head == head->next) //判断链表是否为空,为空直接退出 { printf("linkedlist is empty\n"); return false; } while(phead->next) { phead = phead->next; // 遍历链表 if(head->next == phead->next) { printf(" %d",phead->data); // 打印链表 printf("\n"); return false; } printf(" %d",phead->data); // 打印链表 } return true; }
主函数测试
int main()
{
CircLList_t * Head = CircLList_creat();
CircLList_HeadInsert(Head,8);
CircLList_HeadInsert(Head,5);
CircLList_HeadInsert(Head,3);
CircLList_HeadInsert(Head,7);
CircLList_Print(Head); //测试结果:7 3 5 8
CircLList_TailInsert(Head,42);
CircLList_TailInsert(Head,99);
CircLList_TailInsert(Head,87);
CircLList_TailInsert(Head,63);
CircLList_Print(Head); //测试结果:7 3 5 8 42 99 87 63
CircLList_DestInsert(Head,42,11);
CircLList_DestInsert(Head,3,33);
CircLList_DestInsert(Head,87,61);
CircLList_Print(Head); //测试结果:7 3 33 5 8 42 11 99 87 61 63
CircLList_DestDelete(Head,42);
CircLList_Print(Head);
CircLList_HeadDelete(Head);
CircLList_Print(Head);
CircLList_TailDelete(Head);
CircLList_Print(Head); //测试结果: 3 33 5 8 11 99 87 61
return 0;
}
已经经过测试验证并成功,测试结果如代码的测试结果一致,如图:

希望以上的代码能对同是初学者的你有所帮助,一起加油吧,小白们

浙公网安备 33010602011771号