盛年不重来,一日难再晨。及时宜自勉,岁月不待人。
——陶渊明
目录
1.头插法建立双向循环链 图示 代码 2.尾插法建立双向循环链表 图示 代码 3.查找结点
4.获取链表长度 5.删除第i个结点 图示 代码 6.在第i个位置之前插入新结点 图示 代码
7.输出链表 8.修改第i个结点的值 9.销毁链表 10.清空链表
一、前言
学习了单链表,我们发现每个结点只能找到其后继结点,而不能找到前驱结点,我们可以思考能否再加一个指针域,使其能够访问其前驱结点,这就是双链表的由来。因此双向循环链表的意思就是一个结点有两个指针域,分别指向直接前驱结点和直接后继结点,并且尾结点指向头结点,这样的好处是什么呢?
好处就是可以直接访问某个结点的前驱结点,并且由任意一个结点出发均能找到表中所有结点。这在单链表中是不能实现的。应用在双向查询的场合,但双向循环链表的缺点就是占用内存大,而且操作稍微复杂。
二、双向循环链表图示
三、双链表的存储结构
typedef struct DuLNode{
int data;
struct DuLNode * prior;//前驱结点
struct DuLNode * next; //后继结点
}DuLNode,* DuLinkList;
/* 最后一句的等同于下面代码,中括号是我自己加上的,只是提示把他们看作是一个整体
typedef {struct DuLNode } DuLNode; // struct DuLNode 的别名是 DuLNode
typedef {struct DuLNode * } DuLinkList; // struct DuLNode * 的别名是 DuLinkList
*/
四、常用操作的实现
1.头插法建立双向循环链表
图示
代码
//1.头插法建立线性表
Status create_headLinked_List(DuLinkList & L,int n)
//逆序输入n个元素的值,建立带表头结点双向循环链表L
{
DuLinkList head = (DuLinkList)malloc(sizeof(DuLNode));//头结点的存储地址
if(NULL == head) return ERROR; //分配空间失败
L = head; //头指针L存储头结点P的地址,即头指针指向头结点
L->data = 0; //用头结点统计元素个数,刚开始为0个
L->prior = L; //链表为空,头结点prior指向自己
L->next = L; //链表为空,头结点next指向自己
//由此得出链表为空条件: L->next == L->prior == L
for(int i = 0;i<n;i++)
{
DuLinkList List_Node = (DuLinkList)malloc(sizeof(DuLNode));
if(NULL == List_Node) return ERROR;
scanf("%d",&List_Node->data);
L->data++;
List_Node->prior = head;
List_Node->next = head->next;
head->next->prior = List_Node; //头插法,即链表中的元素顺序与输入相反
head->next = List_Node;
}
return OK;
}
2.尾插法建立双向循环链表
图示
代码
//2.尾插法创建带头结点的链表
Status tail_create_heahLinked_List(DuLinkList & L,int n)
{
DuLinkList head = (DuLinkList)malloc(sizeof(DuLNode));//头结点的存储地址
if(NULL == head) return ERROR;
L = head; //头指针L存储头结点P的地址,即头指针指向头结点
L->data = 0; //用头结点统计元素个数,刚开始为0个
L->prior = L;
L->next = L;
DuLinkList List_Node = NULL;
for(int i = 0;i<n;i++)
{
List_Node = (DuLinkList)malloc(sizeof(DuLNode));
if(NULL == List_Node) return ERROR;
scanf("%d",&List_Node->data);
L->data++;
List_Node->prior = head->prior;
List_Node->next = head; //每插入一个元素,头结点统计链表元素个数加一
head->prior->next = List_Node; //尾插法,即链表中的元素顺序与输入相同
head->prior = List_Node;
}
return OK;
}
3.查找结点
//3.查找某一个数据是否在链表中,如果没有,返回false,否则返回该结点的位序
Status getDuLNodeIndex(DuLinkList L,int number)
{
int i=1;
DuLinkList temp =L->next;
while(temp != L )
{
if(number == temp->data) return i;
temp = temp->next;
i++;
}
return FALSE;
}
4.获取链表长度
//4.获取链表长度
Status getLength(DuLinkList L)
{
return L->data;
}
5.删除第i个结点
图示
代码
//5.删除某个位置节点 (从1开始)
Status deleteDuLNode(DuLinkList L ,int i,int & e) //用e保存删除的节点
{
int j=1;
if(L->next == L && L->prior ==L ) return ERROR; //表为空
if(j > i || i>L->data) return ERROR; //说明位置不合理,直接返回
DuLinkList temp = L;
while(j<=i)
{
temp = temp->next;
j++;
}
e = temp->data;
temp->next->prior = temp->prior;
temp->prior->next = temp->next;
free(temp);
L->data--;
return OK;
}
6.在第i个位置之前插入新结点
图示
代码
//6.在第i个位置之前插入新结点
Status insertDuLNode(DuLinkList L ,int i,int value)
{
int j=1;
if( j > i || i>L->data + 1 ) return ERROR;//允许在最后一个元素之后插入元素
DuLinkList temp = L;
DuLinkList newDuLNode = (DuLinkList)malloc(sizeof(DuLNode));
while(j<i)
{
temp = temp->next;
j++;
}
newDuLNode->data = value;
newDuLNode->next = temp->next;
newDuLNode->prior = temp;
temp->next->prior = newDuLNode;
temp->next = newDuLNode;
L->data++;
return OK;
}
7.输出链表
//7.输出带头结点链表中的元素
void headlinked_List_Traverse(DuLinkList L)
{
printf("该链表的元素个数为:%d \n",L->data);
DuLinkList p = L->next;
while(p != L)
{
printf(" %d ",p->data);
p = p->next;
}
}
8.修改第i个结点的值
//8.修改第i个结点的值
Status updateDuLNode(DuLinkList L,int i,int value)
{
int j=1;
if(L->next == L && L->prior ==L ) return ERROR;//表为空
if(j > i || i>L->data) return ERROR; //说明位置不合理,直接返回
DuLinkList temp = L;
while(j<=i )
{
temp = temp->next;
j++;
}
temp->data = value;
return OK;
}
9.销毁链表
//销毁链表,包括头结点的所有结点全部释放
Status destroy_headlinked_list(DuLinkList & L)
{
L->data = 0;
DuLinkList p = NULL;
while(L)
{
p = L->next;
free(L);
L = p;
}
L = NULL;
return OK;
}
10.清空链表
Status clear_headlinked_list(DuLinkList L)
{
L->data = 0;
DuLinkList p,q;
p = q = L->next;
while(p != L)
{
q = p->next;
free(p);
p = q;
}
L->next = L->prior = L;
return OK;
}
五、源代码
#include<cstdio>
#include<cstdlib>
#include<malloc.h>
#include<cstring>
#include<conio.h>
//函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
typedef int Status; //int 的别名,其值是函数结果状态代码
typedef struct DuLNode{
int data;
struct DuLNode * prior;//前一个结点
struct DuLNode * next; //后一个结点
}DuLNode,* DuLinkList;
/* 最后一句的等同于下面代码,中括号是我自己加上的,只是提示把他们看作是一个整体
typedef {struct DuLNode } DuLNode; // struct DuLNode 的别名是 DuLNode
typedef {struct DuLNode * } DuLinkList; // struct DuLNode * 的别名是 DuLinkList
*/
//1.建立线性表
Status create_headLinked_List(DuLinkList & L,int n)
//逆序输入n个元素的值,建立带表头结点双向循环链表L
{
DuLinkList head = (DuLinkList)malloc(sizeof(DuLNode));//头结点的存储地址
if(NULL == head) return ERROR; //分配空间失败
L = head; //头指针L存储头结点P的地址,即头指针指向头结点
L->data = 0; //用头结点统计元素个数,刚开始为0个
L->prior = L; //链表为空,头结点prior指向自己
L->next = L; //链表为空,头结点next指向自己
//由此得出链表为空条件: L->next == L->prior == L
head->next = head;
head->prior = head;
for(int i = 0;i<n;i++)
{
DuLinkList List_Node = (DuLinkList)malloc(sizeof(DuLNode));
if(NULL == List_Node) return ERROR;
scanf("%d",&List_Node->data);
L->data++;
List_Node->prior = head;
List_Node->next = head->next;
head->next->prior = List_Node; //头插法,即链表中的元素顺序与输入相反
head->next = List_Node;
}
return OK;
}
//2.尾插法创建带头结点的链表
Status tail_create_heahLinked_List(DuLinkList & L,int n)
{
DuLinkList head = (DuLinkList)malloc(sizeof(DuLNode));//头结点的存储地址
if(NULL == head) return ERROR;
L = head; //头指针L存储头结点P的地址,即头指针指向头结点
L->data = 0; //用头结点统计元素个数,刚开始为0个
L->prior = L;
L->next = L;
DuLinkList List_Node = NULL;
for(int i = 0;i<n;i++)
{
List_Node = (DuLinkList)malloc(sizeof(DuLNode));
if(NULL == List_Node) return ERROR;
scanf("%d",&List_Node->data);
L->data++;
List_Node->prior = head->prior;
List_Node->next = head; //每插入一个元素,头结点统计链表元素个数加一
head->prior->next = List_Node; //尾插法,即链表中的元素顺序与输入相同
head->prior = List_Node;
}
return OK;
}
//3.查找某一个数据是否在链表中,如果没有,返回false,否则返回该结点的位序
Status getDuLNodeIndex(DuLinkList L,int number)
{
int i=1;
DuLinkList temp =L->next;
while(temp != L )
{
if(number == temp->data) return i;
temp = temp->next;
i++;
}
return FALSE;
}
//4.获取链表长度
Status getLength(DuLinkList L)
{
return L->data;
}
//5.删除某个位置节点 (从1开始)
Status deleteDuLNode(DuLinkList L ,int i,int & e)//用e保存删除的节点
{
int j=1;
if(L->next == L && L->prior ==L ) return ERROR;//表为空
if(j > i || i>L->data) return ERROR;//说明位置不合理,直接返回
DuLinkList temp = L;
while(j<=i)
{
temp = temp->next;
j++;
}
e = temp->data;
temp->next->prior = temp->prior;
temp->prior->next = temp->next;
free(temp);
L->data--;
return OK;
}
//6.在第i个位置之前插入新结点
Status insertDuLNode(DuLinkList L ,int i,int value)
{
int j=1;
if( j > i || i>L->data + 1 ) return ERROR;//允许在最后一个元素之后插入元素
DuLinkList temp = L;
DuLinkList newDuLNode = (DuLinkList)malloc(sizeof(DuLNode));
while(j<i)
{
temp = temp->next;
j++;
}
newDuLNode->data = value;
newDuLNode->next = temp->next;
newDuLNode->prior = temp;
temp->next->prior = newDuLNode;
temp->next = newDuLNode;
L->data++;
return OK;
}
//7.输出带头结点链表中的元素
void headlinked_List_Traverse(DuLinkList L)
{
printf("该链表的元素个数为:%d \n",L->data);
DuLinkList p = L->next;
while(p != L)
{
printf(" %d ",p->data);
p = p->next;
}
}
//8.修改第i个结点的值
Status updateDuLNode(DuLinkList L,int i,int value)
{
int j=1;
if(L->next == L && L->prior ==L ) return ERROR;//表为空
if(j > i || i>L->data) return ERROR;//说明位置不合理,直接返回
DuLinkList temp = L;
while(j<=i )
{
temp = temp->next;
j++;
}
temp->data = value; //结点
return OK;
}
//输出不带头结点链表中的元素
void linkedListTraverse(DuLinkList L)
{
DuLinkList p = L;
while(p)
{
printf(" %d ",p->data);
p = p->next;
}
}
/*清空链表跟销毁链表的区别 ... 销毁:是先销毁了链表的头,然后接着一个一个的把后面的销毁了,
这样这个链表就不能再使用了,即把包括头的所有节点全部释放。 清空:是先保留了链表的头,
然后把头后面的所有的都销毁,最后把头里指向下一个的指针设为空,
这样就相当与清空了,但这个链表还在,还可以继续使用;即保留了头,后面的全部释放。*/
//销毁链表,包括头结点的所有结点全部释放
Status destroy_headlinked_list(DuLinkList & L)
{
L->data = 0;
DuLinkList p = NULL;
while(p != L)
{
p = L->next;
free(L);
L = p;
}
L = NULL;
return OK;
}
/*清空带头结点链表
1.先保留链表的头结点
2.然后把头结点后面的所有的都销毁
3.最后把头结点里指向首元结点的指针设为空 */
Status clear_headlinked_list(DuLinkList L)
{
L->data = 0;
DuLinkList p,q;
p = q = L->next;
while(p != L)
{
q = p->next;
free(q);
p = q;
}
L->next = L->prior = L;
return OK;
}
//销毁不带头结点的链表
Status destroy_linked_list(DuLinkList L)
{
DuLinkList p;
while(L)
{
p = L->next;
free(L);
L = p;
}
return OK;
}
int main(void)
{
DuLinkList L;//头指针L
int n = 0;
printf("请输入所要创建带头结点链表的元素个数:");
scanf("%d",&n);
printf("\n\n请输入所要创建带头结点链表的元素数据:");
//if(create_headLinked_List( L, n) ) //头插法
if( tail_create_heahLinked_List( L, n) ) //尾插法
headlinked_List_Traverse(L);
int number=0;
printf("\n请输入查找的元素:");
scanf("%d",&number);
if(getDuLNodeIndex(L,number))
printf("%d",getDuLNodeIndex(L, number));
else printf("未找到! ");
printf("\n该链表的长度为:%d",getLength(L));
int i=1,value,e;
printf("\n请输入删除结点的位置:");
scanf("%d",&i);
if(deleteDuLNode( L ,i,e))
printf("\n删除结点%d成功\n",e);
else
printf("\n删除结点%d失败\n",e);
headlinked_List_Traverse(L);
printf("\n该链表的长度为:%d",getLength(L));
printf("\n请输入插入结点的位置和数据:");
scanf("%d %d",&i,&value);
insertDuLNode( L ,i, value);
printf("\n该链表的长度为:%d",getLength(L));
headlinked_List_Traverse(L);
printf("\n请输入待修改结点的位置和数据:");
scanf("%d %d",&i,&value);
updateDuLNode(L,i, value);
headlinked_List_Traverse(L);
if(destroy_headlinked_list(L))
printf("\n销毁带头结点链表L成功!\n");
else printf("销毁失败!!\n");
return 0;
}
六、总结
1.理解双向循环链表的关键就是理清链表的首尾关系,即临界点,另外就是要勤加练习双向思考,多实践
2.每插入或者删除一个结点时,都要考虑前驱后继的关系
3.双向循环链表可以做链表的逆序输出、双向查找等等,有着很多的优点,代价就是多了一个指针域