循环链表之双循环链表(C语言版)
接着上一篇博文http://12172969.blog.51cto.com/12162969/1918256,把循环链表里的双循环链表的基本操纵按照我个人的理解进行总结一下。
宁城网站建设公司成都创新互联公司,宁城网站设计制作,有大型网站制作公司丰富经验。已为宁城上千提供企业网站建设服务。企业网站搭建\外贸营销网站建设要多少钱,请找那个售后服务好的宁城做网站的公司定做!
依然沿袭过去的风格,进入双循环链表之前,先提另一种结构,双向链表,先有了双向链表再有了双循环链表。这两种结构和单链表一样都有带头结点和不带头结点之分。我们先来看一下这几种结构的结构图:
双链表
双循环链表
既然单向链表有普通的链表也就是不循环的链表、那么双向链表也一样,也有普通的双向链表和双向循环链表;也有带头结点,不带头结点的结构,这里依然是两种结构都给出算法,但是普通的循环链表不写,这里只写循环双链表。
首先我们先给出结构定义等部分的代码:
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;
struct node *prev;
}Node;
typedef struct node *SeqNode;
//定义管理链表的结构
typedef struct list
{
struct node *phead;
struct node *ptail;
size_t length;
}List;
//判断结点是否为空,空返回FALSE,不空返回TRUE;
Status IsEmpty(SeqNode p)
{
if(NULL ==p)
{
return FALSE;
}
return TRUE;
}
//构造新结点,写后面的插入函数后会重复使用构造节点的代码,因此将重复代码封装在函数中减少重复代码量
Node *BuyNode(ElemType x)
{
SeqNode s = (Node*)malloc(sizeof(Node));
if(FALSE == IsEmpty(s))
{
printf("out of mommery\n");
return FALSE;
}
s->data = x;
s->next = NULL;
s->prev = NULL;
return s;
}
//查找函数:按照值插入,都需要找到需要插入的位置,为了优化代码,因此将这一部分封装起来。
//查找要插入的结点,因为双循环链表可以通过当前节点找到自己的地址,因此不需用额外的变量保存当前的地址,
//查找成功返回当前结点的地址,查找失败返回最后一个空间的地址
Node *Find(List head,SeqNode s,ElemType x)
{
while(head.length--)
{
if(s->data < x)
{
s = s->next;
continue;
}
return s;
}
return s;
}
//查找函数:按照值删除,都需要找到需要插入的位置,为了优化代码,因此将这一部分封装起来。
//查找要插入的结点,因为双循环链表可以通过当前节点找到自己的地址,因此不需用额外的变量保存当前的地址,
//查找成功返回当前结点的地址,查找失败返回NULL
Node *_Find(List head,SeqNode s,ElemType x)
{
while(head.length--)
{
if(s->data == x)
{
return s;
}
s = s->next;
}
return NULL;
}
初始化
初始化的时候,就是要建立一个空表,上图中已经给出了空表的示意图,那么 初始化就是建立这样一个结构。
//初始化带头结点的双向循环链表,初始化成功返回TRUE,失败FALSE.
Status Init_Yes_SeqNode(List *head)
{
SeqNode s = BuyNode(0);
if(FALSE == IsEmpty(s))
{
printf("初始化失败\n");
return FALSE;
}
head->length = 0;
head->phead = s;
head->ptail = s;
s->next = s;
s->prev = s;
return TRUE;
}
//初始化不带头结点的循环双链表,初始化成功返回TRUE,失败FALSE.
Status Init_No_Head(List *head)
{
head->length = 0;
head->phead = NULL;
head->ptail = NULL;
return FALSE;
}
双循环链表头插
个人一直觉得只要结构弄清了,写代码就好办了。所以我继续给出头插的示意图:
带不带头结点的双循环链表头插只要按照图中标号顺序,执行就可以头插成功,两者都有一个特殊情况,就是空表的时候,需要维护结构的完整性,需要单独处理。当然我给出的顺序只是其中一种,其他的顺序同样可以达到相同的作用。
具体代码实现如下
//带头结点的的双循环双链表头插,插入成功返回TRUE,失败返回FALSE
Status Insert_Yes_Head(List *head,ElemType x)
{
SeqNode s = BuyNode(x);
//需要注意这里SeqNode 定义的变量是一个结点类型的指针,BuyNode(x)函数是我之前定义的函数,用来构造结点的,后边不在说明
if(FALSE == IsEmpty(s))
{
printf("out of memory\n");
return FALSE;
}
s->next = head->phead->next;
s->prev = head->phead;
s->next->prev = s;
s->prev->next = s;
if(0 == head->length) //处理空表的情况
{
head->ptail = s;
}
head->length++;
return TRUE;
}
//不带头结点的循环双链表头插,插入成功返回TRUE,失败返回FALSE
Status Insert_No_Head(List *head,ElemType x)
{
SeqNode s = BuyNode(x);
if(FALSE == IsEmpty(s))
{
printf("out of memory\n");
return FALSE;
}
if(0 == head->length)
{
s->next = s;
s->prev = s;
head->phead = s;
head->ptail = s;
}
s->next = head->phead;
s->prev = head->ptail;
s->next->prev = s;
s->prev->next = s;
head->phead = s;
head->length++;
return TRUE;
}
双循环链表尾插
我们继续看图:
图看明白了,只要按照步骤来写带就可以了。
具体的实现代码如下:
//带头结点的双循环链表的尾插,插入成功返回TRUE,失败返回FALSE
Status Insert_Yes_Tail(List *head,ElemType x)
{
SeqNode s = BuyNode(x);
if(FALSE == IsEmpty(s))
{
printf("out of memory\n");
return FALSE;
}
s->next = head->phead;
s->prev = head->ptail;
s->prev->next = s;
s->next->prev = s;
head->ptail = s;
head->length++;
return TRUE;
}
//不带头结点的双循环链表的尾插,插入成功返回TRUE,失败返回FALSE
Status Insert_No_Tail(List *head,ElemType x)
{
SeqNode s = BuyNode(x);
if(FALSE == IsEmpty(s))
{
printf("out of memory\n ");
return FALSE;
}
if(0 == head->length) //处理空链表的情况
{
head->phead = s;
head->ptail = s;
s->next = s;
s->prev = s;
}
s->next = head->phead;
s->prev = head->ptail;
s->next->prev = s;
s->prev->next = s;
head->ptail = s;
head->length++;
return TRUE;
}
双循环链表的按值插(这里是按照从小到大的顺序)
双循环链表按值插入(这里是从小到大的顺序),无论是带头结点还是不带头结点,当是空表的时候,直接插入链表,不需用查找插入位置。
其他情况调用Find函数查找要插入的位置,Find函数的返回值是要插入的地址的后一个元素的地址。由于双循环链表可以同过当前元素找到前一个结点所以Find返回来的地址就可以完成插入操作。当是空表的时候,与头插或者尾插的空表示意图一样,读者返回到前边看,这里不给出示意图,这里只给出有元素的按值插入示意图:
不带头结点的按值插入,有一点需要注意,当只有一个结点的时候再次插入第二个结点的时候需要修改phead和ptail的指向,由于只有一个结点,不管是第二个要插入的值比第一个大还是小,由于是循环链表,Find函数返回的都是第一个结点,这样就需要在插如函数中,进行单独处理。
由于带头结点,所以不会出现不带头结点那种需要单独处理的特殊情况,但是带头节点与不带头结点都有共同的特殊情况,那就是空表时的情况。
具体代码实现:
//带头结点的按值插入,插入成功返回TRUE,失败返回FALSE。
Status Insert_Yse_Value(List *head,ElemType value)
{
SeqNode s = BuyNode(value);
SeqNode p = NULL;
if(FALSE == IsEmpty(s))
{
printf("out of memory\n");
return FALSE;
}
if(0 == head->length) //处理空表的情况
{
s->next = head->phead;
s->prev = head->phead;
head->phead->next = s;
head->ptail = s;
head->phead->prev = s;
head->length++;
return TRUE;
}
p = Find(*head,head->phead->next,value); //Find函数,之前定义的,返回要插入的位置的地址。
//这里我提一下这个判断条件,给Find函数传过去的地址,是首元结点的地址,所以当Find()函数返回的地址为head->phead时,说明该元素需要插入到表尾,所以就需要修改ptial的指向。
if(head->phead == p)
{
head->ptail = s;
}
s->next = p;
s->prev = p->prev;
s->prev->next = s;
s->next->prev = s;
head->length++;
return TRUE;
}
//不带头结点的按值插入,插入成功返回TRUE,失败返回FALSE。
Status Insert_No_Value(List *head,ElemType value)
{
SeqNode s = BuyNode(value);
SeqNode p = NULL;
if(FALSE == IsEmpty(s))
{
printf("out of memory\n");
return FALSE;
}
if(0 == head->length) //处理空表的情况
{
head->phead = s;
head->ptail = s;
s->next = s;
s->prev = s;
head->length++;
return TRUE;
}
p = Find(*head,head->phead,value); //Find函数,之前定义的,返回要插入的位置的地址。
s->next = p;
s->prev = p->prev;
p->prev = s;
s->prev->next = s;
//重点说一下这一部分,由于没有头结点,所以当Find()函数head->phead时,有可能是往头插,也有可能是往尾插,所以就需要进行判断,当Find()函数的返回值等于头指针时,如果将要插入的值大于首元结点的值,那么就是说,要插入的值是往最后插,即就是需要把ptail修改指向新结点。否则修改phead。
if(head->phead == p)
{
if( s->data > head->ptail->data)
{
head->ptail = s;
}
else
{
head->phead = s;
}
}
head->length++;
return TRUE;
}
循环双链表的头删
不论是带头结点的链表还是不带头结点当链表为空时直接返回FALSE;对于带头结点和不带头结点的双循环链表,都需要注意一个结点的情况,对于带头结点的双循环链表,由于phead始终是指向头结点的,所以,不需用修改头指针的指向,以及最后一个节点的next域除了当删除到最后一个结点时,其他的都不用修改phead、ptail、ptail->next、phead->prev。然而对于不带头结点就需要每次维护循环链表的完整性,修改这些值。我们继续看图:
具体代码实现:
//带头结点的循环双链表的头删,删除失败返回FALSE,删除成功返回TRUE.
Status Delite_Yes_Head(List *head)
{
SeqNode p = head->phead->next;
if(head->phead == p)
{
printf("链表中已空,无元素可删\n");
return FALSE;
}
head->phead->next = p->next;
p->next->prev = head->phead;
free(p);
p = NULL;
if(1 == head->length) //处理删除时只有一个结点时的情况
{
head->ptail = head->phead;
}
head->length--;
return TRUE;
}
//不带头结点的头删,删除失败返回FALSE,删除成功返回TRUE.
Status Delite_No_Head(List *head)
{
SeqNode p = head->phead;
if(NULL == p)
{
printf("链表已空,已经无元素可以删除\n");
return FALSE;
}
if(1 == head->length) //处理删除时只有一个结点时的情况
{
head->phead = NULL;
head->ptail = NULL;
}
else //处理一般情况
{
head->phead = p->next;
p->next->prev = head->phead;
head->ptail->next = head->phead; //维护循环链表的完整性
head->phead->prev = head->ptail;
}
free(p);
p = NULL;
head->length--;
return TRUE;
}
循环双链表的尾删
由于尾删每次都需要修改ptail的指向,并且修改头结点的prev的指向,所以对于带头结点的双循环链表,所有情况都是一样的不需用单独处理那种情况,然而对于不带头结点的双循环链表,由于删除最后一个结点后phead和ptail都会指向NULL,所以要对不带头结点的双循环链表的一个结点的情况单独处理。ok,能用图解决的问题觉绝不废话,当然这是玩笑,好了我们来看图:
具体代码实现:
//带头结点的尾删,删除成功返回TRUE,失败返回FALSE;
Status Delite_Yes_Tail(List *head)
{
SeqNode p = head->ptail;
if(head->phead == p)
{
printf("链表已空、已经无元素可以删除\n");
return FALSE;
}
p->prev->next = head->phead;
head->phead->prev = p->prev;
head->ptail = p->prev;
free(p);
p = NULL;
head->length--;
return TRUE;
}
//不带头结点的循环双链表的尾删
Status Delite_No_Tail(List *head)
{
SeqNode p = head->ptail;
if(NULL == p)
{
printf("链表已空、已经无元素可以删除\n");
return FALSE;
}
if(1 == head->length) //处理只有一个结点的情况
{
head->phead = NULL;
head->ptail = NULL;
}
else
{
p->prev->next = head->phead;
head->phead->prev = p->prev;
head->ptail = p->prev;
}
free(p); //释放结点空间
p = NULL;
head->length--;
return TRUE;
}
循环双链表的尾删
为了便于操作,这里就要用到之前写的另一个Find函数,注意这两个Find是不一样的这里Find函数名是_Find函数名前有个下划线,这只是从名字上的差别,它的功能也不同_Find函数在链表中搜索一个特定值,如果搜索到返回当前地址,没有搜索到返回NULL。
然后对于带头结点的双循环单链表,就有这么几种情况,1、如果表为空,直接返回不用查找了。2、如果_Find返回NULL,说明表中没有此元素,所以也删除失败,3、再就是找到元素,返回它的地址,将其删除掉释放掉空间,这里需要注意,由于带头结点phead始终指向头结点,绝不需要修改phead的指向,但是当删除ptail指向的结点时就需要修改指向。这个要单独处理。
对于不带头结点的双循环链表。1、同样如果链表为空,不用查找直接返回,删除失败;2、如果_Find返回NULL,说明表中没有此元素,所以也删除失败;3、再就是找到元素,对于不带头结点的链表,又分为四种情况:a、如果phead == ptail 并且 _Find 返回的地址等于他们说明链表只有一个结点,并且要把他删除了;b、如果_Find 返回的地址 == ptail 就需要修改 ptail的指向以及附带的指向;c、如果_Find 返回的地址 == phead 就需要修改phead的指向以及附带的指向。d、再就是一般情况,注意这几个条件判断是有顺序的,如果没有顺序就需要b和c再添加条件。接下来我们继续不看图了,直接上代码,这篇都画了那么多图了,并且这里会出现的情况,也就是之前那么多删除操作的综合,ok,所以没有图咯,不上图了呢,代码呢我就会给出尽可能多的注释。
//带头结点的按照值,删除,这里先不考虑重复值,删除成功返回TRUE,失败返回FALSE;
Status Delite_Yes_Value(List *head,ElemType value)
{
SeqNode p = NULL;
if(0 == head->length) //表为空,不用查找,删除失败,直接返回FALSE
{
printf("链表已空,%d删除失败\n",value);
return FALSE;
}
p = _Find(*head,head->phead->next,value);
if(NULL == p) //查找完成,表中不存在特定值的情况,删除失败,返回FALSE
{
printf("%d删除失败,链表中没有%d\n",value,value);
return FALSE;
}
if(head->ptail == p ) //删除尾结点的情况,就需要修改ptail指向
{
head->ptail = head->ptail->prev;
}
p->prev->next = p->next;
p->next->prev = p->prev;
free(p);
p = NULL;
head->length--;
return TRUE;
}
//不带头结点的按照值删除,这里不考虑重复值,只删除第一个遇到的,删除成功返回TRUE,失败返回FALSE;
Status Delite_No_Value(List *head,ElemType value)
{
SeqNode p = head->phead;
if(FALSE == IsEmpty(p)) //表为空,不用查找,删除失败,直接返回
{
printf("链表已空,%d删除失败\n",value);
return FALSE;
}
p = _Find(*head,head->phead,value);
if(NULL == p) //查找完成,表中不存在特定值的情况,删除失败,返回FALSE
{
printf("%d删除失败,链表中没有%d元素\n",value,value);
return FALSE;
}
//表中元素只有一个并且要删除的元素就是这一个,就需要修改phead 和 ptail的指向
if(head->phead == head->ptail && p == head->phead)
{
head->phead = NULL;
head->ptail = NULL;
}
//删除头结点的情况,就需要修改phead指向,以及附带的其他指向,使其保持循环结构的完整性
else if(head->phead == p)
{
head->phead = head->phead->next;
head->ptail = head->phead;
}
//删除尾结点的情况,就需要修改ptail指向,以及附带的其他指向,使其保持循环结构的完整性
else if(head->ptail == p)
{
head->ptail = head->ptail->prev;
head->phead->prev = head->phead;
}
//一般情况
p->prev->next = p->next;
p->next->prev = p->prev;
free(p);
p = NULL;
head->length--;
return TRUE;
}
循环双链表的打印输出
//打印输出不带头结点的循环双链表中所有元素。
void Show_No(List head)
{
SeqNode p = head.phead;
if(NULL == p)
{
printf("链表为空\n");
return ;
}
if(1 == head.length)
{
printf("%d ",p->data);
printf("\n");
return ;
}
while(head.phead != p->next)
{
printf("%d ",p->data);
p = p->next;
}
printf("%d ",p->data);
printf("\n");
}
//打印输出带头结点的循环双链表中所有元素。
void Show_Yes(List head)
{
SeqNode p = head.phead->next;
while(head.phead != p)
{
printf("%d ",p->data);
p = p->next;
}
printf("\n");
}
写到这里循环链表的基本操作就写完了,当然有几个操作没有写,比如排序,这里先不写,后边会有一个专题是写排序的。还有获取链表长度等大家都很容易实现的就没有写,主要把带头结点不带头结点的插入删除对比的实现。后续会继续完善。好了以上就是双循环链表的基本操作,双循环链表写完了,线性表也就告一段落了。ok,从线性表到链表,我把带头节点不带头结点的基本操作都尽可能多的实现了,我们会发现带头结点的操作会方便了好多。接下来,数据结构继续向前推进
分享文章:循环链表之双循环链表(C语言版)
文章出自:http://azwzsj.com/article/pehcsp.html