链表之循环链表







进入循环链表之前我先解决一下上篇博文最后提到到的一种更方便的管理链表的结构:



typedef struct node  //节点类型

{

type value;

struct node *next;

}Node;

typedef struct list

{

Node *phead;

Node *ptail;

int length;

}List;



   首先来说这种结构只是为了方便管理,对于链表之前提的带头节点与不带头结点所提的区别依然是有效的,ok我们继续上图,先认识一下这种结构:






分别通过phead、ptail指向链表的头和尾,length记录链表的长度,这样就可以方便的记录链表的信息,我们之前说过带头结点的链表方便操作,那么两者加起来就更方便。接下来进入今天的主题,循环链表,为什么会出现循环链表,我个人认为,就是为了弥补单链表的不足,在单链表中,当我们已经遍历过某个元素,当我们希望再次查找时,就需要让临时指针重新指向头首元结点,重新遍历。这样就很麻烦,有了单循环链表,由于尾结点是指向头,就可以直接找到头,不需用额外语句,我想这就是单循环链表出来的缘故。找到头的问题解决了,之前单链表按位置插入删除的时候,当我们在需要插入、删除某个元素的时候不仅要找到特定元素,还需要记住这个元素之前的元素,才能找到特定元素的地址。ok,为了解决这个问题,我们聪明的计算机前辈,又发明了双循环链表,解决了这个问题。接下来,依然为了便于理解,给出我在学习过程中的一些理解图:带头结点、不带头结点的链表,这里都给出。不过,这里不在采用之前给出的管理链表的方式管理链表,通过我刚给出的方式处理,请看下图:






ok看完了结构图,我们来看循环链表主要的基本操作,同样的这里把带头结点的操作,不带头结点的的结构的操作都按照我个人的理解都给出。这里先给出单循环链表的代码以及示意图



首先我们先给出结构定义等部分的代码:



typedef int Status;

#define OK        1

#define ERROR     0

#define TRUE      1

#define FALSE     0

typedef int ElemType;

//定义单链表的结点

typedef struct node

{

ElemType data;

struct node *next;

}Node;

typedef struct node *LinkNode;

//定义管理链表的结构

typedef struct list

{

LinkNode Phead;

LinkNode Ptail;

int length ;

}List;




//构造新结点,写后面的插入函数后会重复使用构造节点的代码,因此将重复代码封装在函数中减少重复代码量

Node * BuyNode(ElemType x)

{

LinkNode s = (Node *)malloc(sizeof(Node));

if(NULL == s)

{

return NULL;

}

s->data = x;

s->next = NULL;

return s;

}

//同样构造完结点后多次使用到判断是否为空,因此也将代码封装成函数精简代码。

//判断是空返回FALSE;不是空返回TRUE;

Status IsEmpty(LinkNode s )

{

if(NULL == s)

{

printf("Out of memory\n");

return FALSE;

}

return TRUE;

}




初始化




//初始化不带头结点的循环单链表,初始化成功返回TRUE;失败返回FALSE;

Status Init_No_Head(List * head)

{

head->length = 0;

head->Phead = NULL;

head->Ptail = NULL;

return TRUE;

}

//初始化带头结点是循环单链表,初始化成功返回TRUE;失败返回FALSE;

Status Init_Yes_Head(List *head)

{

LinkNode s = BuyNode(0);

if(NULL == s)

{

printf("内存不足,初始化失败\n");

return ERROR;

}

head->length = 0;

head->Phead = head->Ptail = s;

s->next = head->Phead;

return TRUE;

}




单循环链表的头插



同样也为了便于理解,我这里给出头插操作的示意图:



带头结点的头插,由于带头结点,所以头指针phead始终指向头结点,但是有一种特殊情况,为了维护其结构的完整性,那么对于空表的头插就是一种特殊情况,就要单独处理,需要把尾指针的指向修改。




由于不带头结点,所以每次头插就需要修改头指针的指向。所以不带头结点的头插就会出现两种情况,1、表中已经有多个元素,头插时需要把phead始终指向第一个结点,这里没有头结点,因此就需要每次需改phead,2、空表的时候,就需要修改头指针phead和尾指针ptail的指向,并且要把第一个结点next指向自己,构成循环。



具体实现代码如下:



//不带头结点的头插,插入成功返回TRUE,失败返回FASLE

Status Insert_No_Head(List *head,ElemType x)

{

LinkNode s = BuyNode(x);                  //构造新结点

if(FALSE == IsEmpty(s))

{

return FALSE;

}

if(0 == head->length)                  //处理空链表的情况

{

head->Ptail = head->Phead = s;

}

s->next = head->Phead;

head->Phead = s;

head->Ptail->next = s;

head->length++;

return TRUE;

}




//带头结点的循环单链表的头插,插入成功返回TRUE,失败返回FASLE

//带头结点的头插需要注意一点,那就是当链表为空时要维护结构的完整性,那么就需要修改ptail的指向

Status Isert_Yse_Head(List *head ,ElemType x)

{

LinkNode s = BuyNode(x);

if(FALSE == IsEmpty(s))

{

return FALSE;

}

s->next = head->Phead->next;

head->Phead->next= s;

if(0 == head->length)            //处理空链表的情况

{

head->Ptail = s;

s->next = head->Phead;

}

head->length++;

return TRUE;

}




单循环链表的尾插



同样我们来看尾插的示意图:



带头结点的尾插不论是空表还是有元素的表处理的情况是一样,每次都要修改ptail的指向



对于不带头结点的链表,需要注意空表时的情况,此时链表为空phead和ptail都指向空,所以就需要修改phead的指向。



具体实现代码如下:



//不带头结点的循环单链表的尾插,插入成功返回RUE,失败返回FALSE

Status Isert_No_Tail(list *head,ElemType x)

{

LinkNode s = BuyNode(x);

if(FALSE == IsEmpty(s))

{

return FALSE;

}

if(0 == head->length)           //处理空链表的情况

{

head->Phead = head->Ptail = s;

}

s->next = head->Phead;

head->Ptail->next = s;

head->Ptail = s;

//带头结点的循环单链表尾插,插入成功返回RUE,失败返回FALSE

Status Insert_Yes_Tail(list *head, ElemType x)

{

LinkNode s = BuyNode(x);

if(FALSE == IsEmpty(s))

{

return FALSE;

}

s->next = head->Phead;

head->Ptail->next = s;

head->Ptail = s;

head->length++;

return TRUE;

}




单循环链表的头删



我们继续来循环单链表的头删的示意图:



对于不带头结点的循环单链表,表中有多个元素的时候,按照步骤修改指向,特别注意一定要释放删除的结点内存,不让会造成内存泄露,并且为了防止产生野指针,把指向删除结点的指针指向NULL;头删一个结点的时候是一个种特殊情况,需要特别关注,需要把phead和ptail 的指向全部修改。



对于带头结点的循环单链表,表中元素较多时,依然是按照图中标的步骤操作,这没得说,当然也需要注意释放删除的结点的空间并把指针指向NULL;带头结点的循环单链表一个结点的时候,也是一种特殊情况。需要把ptail的指向修改,当然释放内存空间,并把指针指向NULL,依然没的说。



具体实现代码如下:



//不带头结点的头删,删除成功返回TRUE,失败失败返回FALSE;

Status Delite_No_Head(list *head,ElemType *x)

{

LinkNode p = head->Phead;

if(NULL == p)

{

printf("链表已空,不能删除了\n");

return FALSE;

}

*x = p->data;

head->Phead = p->next;

head->Ptail->next = head->Phead;

if(1  == head->length)                    //处理只有一个结点的情况

{

head->Phead = NULL;

head->Ptail = NULL;

}

free(p);

p = NULL;

head->length--;

return TRUE;

}

//带头结点的头删,删除成功返回TRUE,失败失败返回FALSE;

Status Delite_Yes_Head(list *head,ElemType *x)

{

LinkNode p = head->Phead->next;

if(head->Phead == p)

{

printf("链表已空,已经无法删除\n");

return FALSE;

}

head->Phead->next = p->next;

*x = p->data;

if(1 == head->length)           //处理只有一个结点的情况

{

head->Ptail = head->Phead;

}

free(p);

p = NULL;

head->length--;

return TRUE;

}




单循环链表的尾删



最后我们来看循环单链表尾删的示意图:





不管是带头结点还是不带头结点的循环单链表的尾删,都需要定位到最后一个结点的前一个结点,只有前一个结点才知道后一个节点的内存,并且,尾删的时候需要修改最后一个结点的前一个结点的指针指向。最后还是要强调一下释放内存空间并且把指针指向指向NULL。需要注意:不带头结点的尾删,当是一个结点的时候需要单独处理。



//不带头结点的尾删,删除成功返回TRUE,失败失败返回FALSE;

Status  Delite_No_Tail(list *head,ElemType *x)

{

LinkNode s = head->Phead;

if(NULL == s)

{

printf("链表已空,已经无元素可以删除\n");

return FALSE;

}

if(1 == head->length)                  //处理只有一个结点的情况

{

head->Phead = head->Ptail = NULL;

*x = s->data;

head->length--;

return TRUE;

}

while(head->Ptail != s->next)         //寻找最后一个结点的前一个结点

{

s = s->next;

}

*x = s->next->data;

head->Ptail = s;

free(s->next);

s->next = head->Phead;

head->length--;

return TRUE;

}

//带头结点尾删,删除成功返回TRUE,失败失败返回FALSE;

Status Delite_Yes_Tail(list *head,ElemType *x)

{

LinkNode s = head->Phead->next;

if(0 == head->length)

{

printf("链表已空,已经无元素可以删除\n");

return FALSE;

}

while(head->Ptail != s->next)  //寻找最后一个结点的前一个结点

{

s = s->next;

}

head->Ptail = s;

*x = s->next->data;

free(s->next);

s->next = head->Phead;

head->length--;

return TRUE;

}




以上就是按照我个人理解总结的单循环链表的基本操作,已经经过测试。上边尽可能详细的给出了操作的主要思想以及示意图,代码还有许多可以优化的地方。希望读者提出意见和建议。双循环链表的基本操作不在本篇总结了,在后续的博文中会对其按照我个人的理解总结。

时间: 2024-08-03 11:25:01

链表之循环链表的相关文章

单链表,双链表,循环链表的区别

单向链表(单链表) 单向链表,它包含两个域,一个信息域和一个指针域.这个链接指向表中的下一个节点,而最后一个节点则 指向一个空值NULL. 单向链表只可向一个方向遍历. 查找一个节点的时候需要从第一个节点开始每次访问下一个节点,一直访问到需要的位置.也可以提前把一个节点的位置另外保存起来,然后直接访问. 双向链表,(双链表) 双向链表中不仅有指向后一个节点的指针,还有指向前一个节点的指针.第一个节点的"前连接"指向NULL,最后一个节点的"后连接"指向NULL. 这

静态链表、循环链表、双向链表(C代码实现)

静态链表 对于没有指针的编程语言,可以用数组替代指针,来描述链表.让数组的每个元素由data和cur两部分组成,其中cur相当于链表的next指针,这种用数组描述的链表叫做静态链表,这种描述方法叫做游标实现法.我们对数组的第一个和最后一个元素做特殊处理,不存数据.让数组的第一个元素cur存放第一个备用元素(第一个未被占用的元素)下标,而数组的最后一个元素cur存放第一个有值的元素下标,相当于头结点作用.空的静态链表如下图 当存放入一些数据时("甲""乙""

单链表,循环链表,双向链表(C++实现)

首先是单链表(带附加表头),实现类代码如下: 1 template<class T> 2 struct LinkNode{//链表节点 3 T data; 4 LinkNode *link; 5 LinkNode(const T& args,LinkNode<T> *ptr=NULL){ 6 data=args; 7 link=ptr; 8 } 9 }; 10 11 template<class T> 12 class List{//带附加头节点的单链表 13

双链表和循环链表

双链表定义 双链表就是在单链表结点上增添了一个指针域,指向当前结点的前驱.这样就可以方便的由其后继来找到其前驱,而实现输出终端结点到开始结点的数据序列. 同样,双链表也分为带头结点的双链表和不带头结点的双链表,情况类似于单链表.带头结点的双链表 head->next 为null的时候链表为空.不带头结点的双链表head为null的时候链表为空. 1.采用尾插法建立双链表 void CreateDlistR (DLNode *&L, int a[], int n){ DLNode*s,*r;

[读书笔记]-大话数据结构-3-线性表(三)-静态链表、循环链表和双向链表

静态链表 对于没有指针的编程语言,可以用数组替代指针,来描述链表.让数组的每个元素由data和cur两部分组成,其中cur相当于链表的next指针,这种用数组描述的链表叫做静态链表,这种描述方法叫做游标实现法.我们对数组的第一个和最后一个元素做特殊处理,不存数据.让数组的第一个元素cur存放第一个备用元素(未被占用的元素)下标,而数组的最后一个元素cur存放第一个有值的元素下标,相当于头结点作用.空的静态链表如下图 当存放入一些数据时("甲""乙""丁&q

循环链表(4) - 分割链表为两段

下面例子演示了如何分割一个链表.使用代码对其进行实现. 原始的循环链表 分割后的循环子链表1 分割后的循环子链表2 1) 使用步进分别为1和2的算法,求得链表的中间指针以及尾指针: 2) 将后半部分链表形成循环链表: 3) 将前半部分链表形成循环链表: 4) 设置两个循环链表的头指针. 在下面的实现中,如果链表中节点个数为奇数,则前半部分链表的个数会比后半部分链表的个数多1. 代码实现: #include <iostream> //链表节点 struct Node { int data; No

数据结构与算法之PHP实现链表类(单链表/双链表/循环链表)

链表是由一组节点组成的集合.每个节点都使用一个对象的引用指向它的后继.指向另一个节点的引用叫做链. 链表分为单链表.双链表.循环链表. 一.单链表 插入:链表中插入一个节点的效率很高.向链表中插入一个节点,需要修改它前面的节点(前驱),使其指向新加入的节点,而新加入的节点则指向原来前驱指向的节点(见下图). 由上图可知,B.C之间插入D,三者之间的关系为 current为插入节点的前驱节点 current->next = new              // B节点指向新节点D new->n

02循环单链表

循环单链表定义:将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成了 一个环,这种头尾相接的单链表成为单循环链表. 循环链表的数据结构: 1 /* c2-2.h 线性表的单链表存储结构 */ 2 struct LNode 3 { 4 ElemType data; 5 struct LNode *next; 6 }; 7 typedef struct LNode *LinkList; /* 另一种定义LinkList的方法 */ 代码实现: 1 2 3 /* bo2-4.c 设立

数据结构Java实现04----循环链表、仿真链表

数据结构Java实现04----循环链表.仿真链表 单向循环链表 双向循环链表 仿真链表 一.单向循环链表: 1.概念: 单向循环链表是单链表的另一种形式,其结构特点是链表中最后一个结点的指针不再是结束标记,而是指向整个链表的第一个结点,从而使单链表形成一个环. 和单链表相比,循环单链表的长处是从链尾到链头比较方便.当要处理的数据元素序列具有环型结构特点时,适合于采用循环单链表. 和单链表相同,循环单链表也有带头结点结构和不带头结点结构两种,带头结点的循环单链表实现插入和删除操作时,算法实现较为