链表分为很多中类型而单项循环链表则是单项链表的进阶,如果你还不会单项链表建议看我前面一篇博客它详细的说明了如何搭建一个单项链表。单项循环链表可以通过尾结点找到首结点而形成一个闭合的环,因此大多数的操作是和单项链表差不多的,只是在插入和删除头尾结点上多了一些 步骤还有在构建链表时要让头结点指向自己。因此我会从构建链表,构建结点,插入和删除结点这四个部分来进行讲解。
第一部分构建链表
在构建链表上我们还是老样子拿一个结构体来装数据域和指针域,然后构建完成结构体后,就需要构建链表,其实单项循环链表的构建和单项链表的构建是差不多的只是在头指针下一个结点指向的时候要指向自己而不是NULL,为什么要指向自己呢,因为单项循环链表头指针记录自己的位置才能算循环。
typedef int datatype_t;
//创建一个结构体,用于装链表的数据域和指针域
typedef struct CircularLinkedlist
{
datatype_t data;
struct CircularLinkedlist *next;
}CLLink_t;
//创建链表,用calloc申请堆内存,然后空链表里应该有一个头结点,然后进行错误处理
//在一个链表中一切都以头结点为中心,只要知道头结点就可以找到后面的的结点
struct CircularLinkedlist *CircularLinnked_CreatHead(void){
//用calloc为头结点分配内存空间,并进行初始化
struct CircularLinkedlist *Head=(struct CircularLinkedlist *)calloc(1,sizeof(CLLink_t));
if (Head == NULL)
{
perror("链表创建失败");
exit(-1);
}
Head->next=Head;
return Head;
}
第二部分构建结点
构建结点还是和单项链表一样用calloc申请堆内存,然后判断内存是否申请成功,然后分别尾结点的数据域和指针域初始化。
//创建新结点,并为新结点进行初始化
struct CircularLinkedlist *Linked_CreatNode(datatype_t data){
struct CircularLinkedlist *New=(struct CircularLinkedlist *)callloc(1,sizeof(CLLink_t));
if (NULL == New)
{
perror("新结点创建失败");
exit(-1);
}
New->data=data;
New->next=NULL;
return New;
}
第三部分结点的插入
在做结点插入的时候我们还是依照惯例看图写代码
这副图是头插法从图中可以知道我们不仅要改头结点的指向还需要改尾结点的指向,因此为了更方便的写代码我们要有一个思路按步骤进行修改首先我们申请一个新结点让新结点的指针域指向首结点,然后让头结点的指针域指向新结点,最后再找到尾结点的位置,让尾结点的指针域指向新结点,因此我们分为3步来完成这个头插法的函数,这是一种情况,还有一种情况是头结点后没有任何结点,头结点是指向自己的,我们就根据这个条件可以判断头结点后无任何结点,我们就可以直接插入结点。
struct CircularLinkedlist *Linked_Ptali(struct CircularLinkedlist *Head){
if (Head==NULL)
{
perror("头结点为空")
exit(-1);
}
struct CircularLinkedlist *P=Head;
while (P->next!=Head)
{
P=P->next;
}
return P;
}
//往单项循环链表中插入元素(头插法)
//分为俩种情况,空链表,有结点的链表
void Linked_InserHead(struct CircularLinkedlist *Head,datatype_t data){
struct CircularLinkedlist* New=Linked_CreatNode(data);
if (Head->next==Head)
{
Head->next=New;
New->next=New;
}
struct CircularLinkedlist* P= Linked_Ptali(Head);
New->next=Head->next;
Head->next=New;
P->next=New;
}
做完头插法后我们接下来就写尾插法的代码,依旧是看图写代码
从图中我们可以看出来我们需要三步来完成这个函数第一步找,找到最后一个结点,但这的判断条件可和单项链表的判断条件不同,循环链表的尾结点总是指向首结点,找到尾结点后让尾结点的指针域指向新结点,然后让新结点的指向域指向首结点。
//使用尾插法插入结点,主要原理是找到最后一个结点然后拿要最后一个结点与要插入结点进行相连
void Linked_InserTali(struct CircularLinkedlist *Head,datatype_t data){
struct CircularLinkedlist *P=Linked_Ptali(Head);
struct CircularLinkedlist* New=NLinked_CreatNode(data);
P->next=New;
New->next=Head->next;
}
指定值插入和单项链表的思路是一样的在这我就不过多的叙述了。
第四部分删除结点
结点的删除我们还是从头部删除和尾部删除来展开细说,首先是头部删除,在做头删时我们还是看图写代码
根据图来说我们可以分为5个步骤来写这个函数,第一找找尾结点,然后改变尾结点的指向,然后备份首结点的地址,然后改变头结点的指向,然后释放首结点。
//删除结点分为3种情况,头删,尾删和中间删除,在做删除之前我们都应该判断链表中是否有结点可以删除
//在做尾删时我们需要找到尾部结点的直接前驱
//在做中间删除的时候我们需要找到要删除结点的直接前驱和。
//头部删除结点,需要判断链表中是否有结点可以删除然后再进行删除结点的操作
//我们首先需要的是备份头结点地址
//删除首结点第一步找到尾结点,然后让尾结点指向首结点的next,然后释放首结点然后再让头结点指向新的首结点
bool Linked_HeadDel(struct CircularLinkedlist*Head){
if (Head->next==Head)
{
perror("链表中没有结点可以删除");
return false;
}
struct CircularLinkedlist*P=Linked_Ptali(Head);
struct CircularLinkedlist* DelNode=Head->next;
P->next=DelNode->next;
Head->next=DelNode->next;
free(DelNode);
return true;
}
做完头删后我们继续来写尾部删除的函数,还是看图写代码
从图中分析我们可以用4步来完成这个函数,第一找到尾结点和尾结点的直接前驱,然后让尾结点的直接前驱的指针域指向首结点,然后释放尾结点。
//尾部删除,在做尾部删除的时候我们应该遍历链表找到最后一个结点,然后再找到最后一个
//结点的直接前驱然后再进行删除操作
//尾部删除找最后一个结点和最后一个结点的直接前驱,第一步找然后链让最后一个结点的直接前驱连接首结点,然后
//释放内存
bool Linked_TaliDel(struct CircularLinkedlist*Head){
//首先先要找到最后一个结点和最后一个结点的直接前驱
//遍历链表
if (Head->next==Head)
{
return false;
}
struct CircularLinkedlist *P=Head->next;
struct CircularLinkedlist *Point=Head;
while (P->next!=Head)
{
Point=P;
P=P->next;
}
Point->next=Head->next;
free(P);
return true;
}
对于指定值删除还是和单项链表的差不多。
代码还有许多的细节问题没处理得行,如果思路上有问题请广大网友积极在评论区纠正。