循环链表之双循环链表




接着上一篇博文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,从线性表到链表,我把带头节点不带头结点的基本操作都尽可能多的实现了,我们会发现带头结点的操作会方便了好多。接下来,数据结构继续向前推进




本文出自 “12162969” 博客,请务必保留此出处http://12172969.blog.51cto.com/12162969/1922954

时间: 2024-07-31 14:32:29

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

双循环链表(包含头指针与尾指针)

双循环链表(包含头指针与尾指针) 以及基本功能的实现 list_ d_link_c.h #ifndef _LIST_D_LINK_C_ #define _LIST_D_LINK_C_ #include<iostream> #include<assert.h> using namespace std; #define ElemType int typedef struct Node { ElemType data; struct Node *prio; struct Node *ne

编程算法 - 有序双循环链表的插入 代码(C)

有序双循环链表的插入 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 有序双循环链表的插入, 需要找到插入位置, 可以采用, 两个指针, 一个在前, 一个在后. 保证前面的小于等于插入值, 后面的大于等于插入值. 特殊情况, 首尾插入(大于或小于整个链表)或单节点, 判断条件为后指针指向首节点. 则需要直接插入. 插入链表头, 需要调整链表头节点. 代码22行. 代码: /* * main.cpp * * Created on: 2014.9.18

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

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

数据结构之—线性表之—双向链表之—浅谈双循环链表

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱.所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点.一般我们都构造双向循环链表. /********************************************************************** * Copyright (c)2015,WK Studios * Filename: Tw_Node.c * Compiler: GCC,VS,VC6.

C语言数据结构——双循环链表的插入操作顺序

双向链表与单链表的插入操作的区别 双向链表因为存在前驱指针和后继指针所以需要修改的指针多于单链表,但指针改动的顺序同样重要 单链表的插入 eg:在节点p的后面插入指针s s->next=p->next;//首先要使要插入的指针指向p->next p->next=s;//再将p的后继指向插入的s即可 注意! 顺序不能调换,否则在将p->next指向s后,原来由p->next指向的节点将会迷失在内存中,很难找到! 双向循环链表的插入 eg:将新的节点插入p节点的后面 s-&

【数据结构】双循环链表(c++)

头文件: #pragma once #include <iostream> #include <assert.h> using namespace std; template<class Type> class List; // 结点类 template<class Type> class NodeList { friend class List<Type>; public: NodeList(); NodeList(Type d, NodeLi

双循环链表

1 #include<stdio.h> 2 #include<stdlib.h> 3 4 #define OK 1 5 #define ERROR 0 6 7 typedef int Elemtype; 8 typedef int Status; 9 10 typedef struct Node{ 11 Elemtype data; 12 struct Node* prior; 13 struct Node* next; 14 }Node; 15 typedef struct No

双循环链表(C++)

#ifndef _DCLIST_ #define _DCLIST_ #include<iostream> using namespace std; #include<assert.h> template <class Type>class DCList; template<class Type> class Node { friend class DCList<Type>; public: Node():data(0),prio(NULL),ne

【数据结构】用C++实现双循环链表的各种操作(包括头删,尾删,插入,逆序,摧毁,清空等等)

//[数据结构]用C++实现单循环链表的各种操作(包括头删,尾删,插入,逆序,摧毁,清空等等) //头文件 #ifndef _CDLIST_H #define _CDLIST_H #include<iostream> using namespace std; template<class Type> class CDList; template<class Type> class ListNode { friend class CDList<Type>; p