当前位置:   article > 正文

【C语言数据结构】双向循环链表_c语言双向循环链表

c语言双向循环链表

目录

前言

 一、双向循环链表

循环结构

1.双向循环链表头文件及函数声明

2.初始化

1.结点构造

2.初始化函数

3.结点申请

 4.数据插入

1.按位置插入

2.尾插

3.头插

 5.查找

 6.数据删除

1.按位置删除

 2.按值删除

3.尾删 

4.头删 

7.清空与销毁 

1.清空

2.销毁

8.双向循环链表源文件及整体函数实现 

总结


前言

这次我们将学习双向循环链表,首先了解双向链表和循环链表的定义和讲解。

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,是单链表的进阶,比单链表的结点结构多出一个指向前驱的指针域,如下图所示:

循环链表是一种链式储存结构,它的最后一个结点指向头结点,形成一个环。因此,从循环链表中的任何一个结点出发都能找到任何其他结点。循环链表的操作和单链表的操作基本一致,差别仅仅在于算法中的循环条件有所不同。方法是将尾结点中的指针域进行使用,指向首结点,如下图所示:

双向循环链表即是将上述二者联合起来使用,以达到完成各类工作的效用,在以后大多数链表结构的使用中,我们都会选择双向循环链表。


 一、双向循环链表

循环结构

 双向循环链表的循环方式是其尾结点的后继指针指向头结点(表头),而头结点的前置指针指向尾结点,达到双向循环的目的,这样不仅使得对链表尾部的操作更为简单,也减少了对NULL指针的引用。

1.双向循环链表头文件及函数声明

新建头文件"dclist.h"(double circle list)对链表函数声明进行保存,方便我们后期查看及使用

  1. #pragma once
  2. #include<stdio.h>
  3. #include<string.h>
  4. #include<assert.h>
  5. #include<ctype.h>
  6. #include<stdlib.h>
  7. typedef int Element;
  8. typedef struct DNode
  9. {
  10. Element data;
  11. DNode* next;
  12. DNode* prev;
  13. }DNode, * PDNode;
  14. //初始化链表
  15. void InitList(PDNode plist);
  16. //购买结点
  17. PDNode BuyNode();
  18. //头插
  19. bool Push_Front(PDNode plist, Element val);
  20. //尾插
  21. bool Push_Back(PDNode plist, Element val);
  22. //按位置插
  23. bool Insert_Pos(PDNode plist, int pos, Element val);
  24. //头删
  25. bool Pop_Front(PDNode plist);
  26. //尾删
  27. bool Pop_Back(PDNode plist);
  28. //按位置删
  29. bool Erease_Pos(PDNode plist, int pos);
  30. //按值删
  31. bool Erease_Val(PDNode plist, Element val);
  32. //查找 返回结点地址
  33. PDNode Find(PDNode plist, Element val);
  34. //判空
  35. bool IsEmpty(PDNode plist);
  36. //获取元素个数
  37. int GetSize(PDNode plist);
  38. //打印链表
  39. void Print(PDNode plist);
  40. //清空
  41. void Clear(PDNode plist);
  42. //销毁
  43. void Destroy(PDNode plist);

 

2.初始化

在对使用链表进行数据的储存之前,我们需要进行链表的初始化

1.结点构造

如图所示,带双向循环链表的结点由三部分构成:

1.数据域

2.指针域1:指向后继结点的指针

3.指针域2:指向前置结点的指针

  1. typedef int Element;//如此对数据类型进行改名操作,若要进行修改数据类型操作会更加方便
  2. typedef struct DNode
  3. {
  4. Element data;
  5. DNode* next;//后继指针
  6. DNode* prev;//前置指针
  7. }DNode, * PDNode;

2.初始化函数

与单链表不同的是,在双向循环链表不存在数据时,头结点的指针域不置为NULL,其后继指针与前置指针都指向头结点本身,以形成最基础的循环形态:

  1. //初始化链表
  2. void InitList(PDNode plist)
  3. {
  4. assert(plist != NULL);
  5. plist->next = plist->prev = plist;
  6. }

3.结点申请

因为链表的结构特性,我们需要对结点进行多次申请,为了方便进行其他函数操作,并且为了优化代码,我们将结点的申请封装于一个函数

  1. //购买结点
  2. PDNode BuyNode()
  3. {
  4. PDNode node = (PDNode)malloc(sizeof(DNode));
  5. if (node == NULL)
  6. {
  7. exit(1);
  8. }
  9. memset(node, 0, sizeof(*node));//将申请的结点内存重置,防止数据残留
  10. return node;
  11. }

 4.数据插入

双向循环链表存在多个指针指向的断开和重新指向,我们在此先进行分析和讲解,如下图所示:

 

按照正常逻辑,我们再重新指定指针指向时,首先需要打破原有的指针指向,即上图中红叉部分,但实际编写程序中,我们会直接修改指针的指向,从而在代码中只会体现重新指向的过程。

在图中,已经标注出4个指针指向的修改,首先,我们应该对新申请的结点中指针(即图中34箭头的指向)进行指定,因为如果我们先对旧有指针进行修改,那么我们可能会丢失结点的地址信息,从而失去我们已经保存的数据。

其中指针具体的指向修改,我们不用太在意顺序,只需要记住先考虑新节点的指向,后修改旧结点的指向即可,下面的代码中,我们将采用4321的顺序:

1.按位置插入

我们先进行通用插入的实现:

  1. //按位置插
  2. bool Insert_Pos(PDNode plist, int pos, Element val)
  3. {
  4. assert(plist != NULL);
  5. //判断插入位置是否合法
  6. if (pos < 0 || pos > GetSize(plist))
  7. {
  8. return false;
  9. }
  10. PDNode node = plist;//将结点指针定位至需要插入数据位置之前的结点
  11. for (int i = 0; i < pos; i++)
  12. {
  13. node = node->next;
  14. }
  15. PDNode newnode = BuyNode();//申请新节点
  16. newnode->data = val;
  17. //以下为插入步骤
  18. newnode->next = node->next;//4
  19. newnode->prev = node;//3
  20. node->next->prev = newnode;//2
  21. node->next = newnode;//1
  22. return true;
  23. }

2.尾插

因为表头的前置指针指向尾结点,所以我们不用像单链表一样进行遍历后才能尾插。

  1. //尾插
  2. bool Push_Back(PDNode plist, Element val)
  3. {
  4. assert(plist != NULL);
  5. PDNode newnode = BuyNode();
  6. newnode->data = val;
  7. //插入
  8. newnode->next = plist;//4
  9. newnode->prev = plist->prev;//3
  10. plist->prev = newnode;//2
  11. plist->prev->next = newnode;//1
  12. return true;
  13. }

3.头插

直接调用按位置插入,插入位置为0

  1. //头插
  2. bool Push_Front(PDNode plist, Element val)
  3. {
  4. assert(plist != NULL);
  5. return Insert_Pos(plist, 0, val);
  6. }

 5.查找

查找与普通链表的查找基本相同,但是在while循环中的结束条件不同,以结点指针是否指向表头来判断

  1. //查找 返回结点地址
  2. PDNode Find(PDNode plist, Element val)
  3. {
  4. assert(plist != NULL);
  5. PDNode node = plist->next;
  6. //遍历链表查找与val 相同的值
  7. while (node != plist && node->data != val)
  8. {
  9. node = node->next;
  10. }
  11. if (plist == node)
  12. {
  13. return NULL;
  14. }
  15. return node;
  16. }

 6.数据删除

双向循环链表的删除大致与单链表相同,但在两相隔结点的重新链接时需要连结两个指针,即被删除结点的前置结点的后继指针指向被删除结点的后继结点,而后继结点的前置指针指向前置结点

1.按位置删除

  1. //按位置删
  2. bool Erease_Pos(PDNode plist, int pos)
  3. {
  4. assert(plist != NULL);
  5. if (pos < 0 || pos >= GetSize(plist) || IsEmpty(plist))
  6. {
  7. return false;
  8. }
  9. PDNode delnode = plist;
  10. for (int i = 0; i <= pos; i++)
  11. {
  12. delnode = delnode->next;
  13. }
  14. //重新连结
  15. delnode->prev->next = delnode->next;
  16. delnode->next->prev = delnode->prev;
  17. free(delnode);
  18. delnode = NULL;
  19. return true;
  20. }

 2.按值删除

这里调用了查找函数,找到val值所在的结点

  1. //按值删
  2. bool Erease_Val(PDNode plist, Element val)
  3. {
  4. assert(plist != NULL);
  5. PDNode delnode = Find(plist, val);
  6. if (delnode == NULL)
  7. {
  8. return false;
  9. }
  10. delnode->prev->next = delnode->next;
  11. delnode->next->prev = delnode->prev;
  12. free(delnode);
  13. delnode = NULL;
  14. return true;
  15. }

3.尾删 

同样的,因为表头的前置指针指向尾结点,则可以减少遍历链表所消耗的资源。

  1. //尾删
  2. bool Pop_Back(PDNode plist)
  3. {
  4. assert(plist != NULL);
  5. if (IsEmpty(plist))
  6. {
  7. return false;
  8. }
  9. PDNode delnode = plist->prev;
  10. delnode->prev->next = delnode->next;
  11. delnode->next->prev = delnode->prev;
  12. free(delnode);
  13. delnode = NULL;
  14. return true;
  15. }

4.头删 

直接调用按位置删除,删除位置为0

  1. //头删
  2. bool Pop_Front(PDNode plist)
  3. {
  4. assert(plist != NULL);
  5. return Erease_Pos(plist, 0);
  6. }

7.清空与销毁 

1.清空

这里我们使用比较简单的方法,一直头删即可,当然这里的尾删也可以使用,消耗资源相同。

  1. //清空
  2. void Clear(PDNode plist)
  3. {
  4. assert(plist != NULL);
  5. while (!IsEmpty(plist))
  6. {
  7. Pop_Front(plist);
  8. }
  9. }

2.销毁

调用清空函数,并使链表达到未初始化的状态。

  1. //销毁
  2. void Destroy(PDNode plist)
  3. {
  4. Clear(plist);
  5. plist->next = plist->prev = NULL;
  6. }

8.双向循环链表源文件及整体函数实现 

  1. #include "dclist.h"
  2. //初始化链表
  3. void InitList(PDNode plist)
  4. {
  5. assert(plist != NULL);
  6. plist->next = plist->prev = plist;
  7. }
  8. //购买结点
  9. PDNode BuyNode()
  10. {
  11. PDNode node = (PDNode)malloc(sizeof(DNode));
  12. if (node == NULL)
  13. {
  14. exit(1);
  15. }
  16. memset(node, 0, sizeof(*node));
  17. return node;
  18. }
  19. //头插
  20. bool Push_Front(PDNode plist, Element val)
  21. {
  22. assert(plist != NULL);
  23. return Insert_Pos(plist, 0, val);
  24. }
  25. //尾插
  26. bool Push_Back(PDNode plist, Element val)
  27. {
  28. assert(plist != NULL);
  29. PDNode newnode = BuyNode();
  30. newnode->data = val;
  31. newnode->prev = plist->prev;
  32. newnode->next = plist;
  33. plist->prev->next = newnode;
  34. plist->prev = newnode;
  35. return true;
  36. }
  37. //按位置插
  38. bool Insert_Pos(PDNode plist, int pos, Element val)
  39. {
  40. assert(plist != NULL);
  41. if (pos < 0 || pos > GetSize(plist))
  42. {
  43. return false;
  44. }
  45. PDNode node = plist;
  46. for (int i = 0; i < pos; i++)
  47. {
  48. node = node->next;
  49. }
  50. PDNode newnode = BuyNode();
  51. newnode->data = val;
  52. newnode->next = node->next;
  53. newnode->prev = node;
  54. node->next->prev = newnode;
  55. node->next = newnode;
  56. return true;
  57. }
  58. //头删
  59. bool Pop_Front(PDNode plist)
  60. {
  61. assert(plist != NULL);
  62. return Erease_Pos(plist, 0);
  63. }
  64. //尾删
  65. bool Pop_Back(PDNode plist)
  66. {
  67. assert(plist != NULL);
  68. if (IsEmpty(plist))
  69. {
  70. return false;
  71. }
  72. PDNode delnode = plist->prev;
  73. delnode->prev->next = delnode->next;
  74. delnode->next->prev = delnode->prev;
  75. free(delnode);
  76. delnode = NULL;
  77. return true;
  78. }
  79. //按位置删
  80. bool Erease_Pos(PDNode plist, int pos)
  81. {
  82. assert(plist != NULL);
  83. if (pos < 0 || pos >= GetSize(plist) || IsEmpty(plist))
  84. {
  85. return false;
  86. }
  87. PDNode delnode = plist;
  88. for (int i = 0; i <= pos; i++)
  89. {
  90. delnode = delnode->next;
  91. }
  92. delnode->prev->next = delnode->next;
  93. delnode->next->prev = delnode->prev;
  94. free(delnode);
  95. delnode = NULL;
  96. return true;
  97. }
  98. //按值删
  99. bool Erease_Val(PDNode plist, Element val)
  100. {
  101. assert(plist != NULL);
  102. PDNode delnode = Find(plist, val);
  103. if (delnode == NULL)
  104. {
  105. return false;
  106. }
  107. delnode->prev->next = delnode->next;
  108. delnode->next->prev = delnode->prev;
  109. free(delnode);
  110. delnode = NULL;
  111. return true;
  112. }
  113. //查找 返回结点地址
  114. PDNode Find(PDNode plist, Element val)
  115. {
  116. assert(plist != NULL);
  117. PDNode node = plist->next;
  118. while (node != plist && node->data != val)
  119. {
  120. node = node->next;
  121. }
  122. if (plist == node)
  123. {
  124. return NULL;
  125. }
  126. return node;
  127. }
  128. //判空
  129. bool IsEmpty(PDNode plist)
  130. {
  131. assert(plist != NULL);
  132. return (plist->next == plist->prev && plist->next == plist);
  133. }
  134. //获取元素个数
  135. int GetSize(PDNode plist)
  136. {
  137. assert(plist != NULL);
  138. PDNode node = plist->next;
  139. int count = 0;
  140. while (node != plist)
  141. {
  142. node = node->next;
  143. count++;
  144. }
  145. return count;
  146. }
  147. //打印链表
  148. void Print(PDNode plist)
  149. {
  150. assert(plist != NULL);
  151. PDNode node = plist->next;
  152. while (node != plist)
  153. {
  154. printf("%d ", node->data);
  155. node = node->next;
  156. }
  157. printf("\n");
  158. }
  159. //清空
  160. void Clear(PDNode plist)
  161. {
  162. assert(plist != NULL);
  163. while (!IsEmpty(plist))
  164. {
  165. Pop_Front(plist);
  166. }
  167. }
  168. //销毁
  169. void Destroy(PDNode plist)
  170. {
  171. Clear(plist);
  172. plist->next = plist->prev = NULL;
  173. }


 

总结

以上为双向循环链表的实现和方法讲解,双向循环链表为比较常用的链表形式,学习并理解双向循环链表会方便以后的学习。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/558379
推荐阅读
相关标签
  

闽ICP备14008679号