• [C语言数据结构]双向循环链表


    目录

    💊1.双向循环链表:

    💊1.1何为双向循环链表

    💊1.2双向循环链表的实现

    💊1.2.1结构体的创建

    💊1.2.2双向循环链表的初始化

    💊1.2.3双向链表的尾插

    void  LTPushBack(LTNode* phead, LTDataType x);

    💊1.2.4双向链表的尾部删除

    💊1.2.5双向链表的头插

    💊1.2.6双向链表的数据的打印

    💊1.2.7双向链表的头部删除数据

    💊1.2.8双向链表寻找数据

    💊1.2.9在双向链表的任意位置插入数据

    💊1.2.10在双向链表的任意位置删除数据


    引:上次我们学习了单链表的实现,相对于双向循环链表来说,单链表的各中操作,比如说增删查改等都显得非常麻烦。所以接下来来学习一下双向循环链表吧!


    💊1.双向循环链表:

    💊1.1何为双向循环链表

     如上所示:每个节点都有包含有两个指针域和一个数据域;
    两个指针域一个存储前一个节点的地址,另一个存储下一个节点的地址;

    这种结构虽然看起来比单链表复杂一些,但是可以简化一系列后来的增删查改的操作;

    💊1.2双向循环链表的实现

    💊1.2.1结构体的创建

    这个结构体的创建和单链表的结构体的创建是大同小异,我们需要两个指针域,一个数据域;

    代码:

    1. typedef int LTDataType;
    2. typedef struct ListNode
    3. {
    4. struct ListNode* next;
    5. struct ListNode* prev;
    6. LTDataType data;
    7. }LTNode;

    💊1.2.2双向循环链表的初始化

    LTNode* InitList(LTNode* Phead);

    这个函数实现的双向循环链表的初始化功能,函数参数的话这边选择了一级指针和单链表的初始化函数不同,单链表所使用的是二级指针;而且初始化函数内部也有所不同;

    代码:

    1. //申请节点函数
    2. LTNode* BuyListNode(LTDataType x)
    3. {
    4. LTNode* node = (LTNode*)malloc(sizeof(LTNode));
    5. if (node == NULL)
    6. {
    7. perror("malloc fail");
    8. exit(-1);
    9. }
    10. node->data = x;
    11. node->next = node->prev = NULL;
    12. return node;
    13. }
    14. //初始化双向链表
    15. LTNode* InitList(LTNode* phead)
    16. {
    17. phead = BuyListNode(-1);
    18. phead->next = phead;
    19. phead->prev = phead;
    20. return phead;
    21. }

    💊1.2.3双向链表的尾插

    void  LTPushBack(LTNode* phead, LTDataType x);

    对于双向循环链表来说,它的尾插非常简单,因为它不需要去遍历链表来找到链表的尾部节点;

    代码:

    1. //双向链表的尾插
    2. void LTPushBack(LTNode* phead, LTDataType x)
    3. {
    4. assert(phead);
    5. LTNode* newnode = BuyListNode(x);
    6. LTNode* tail = phead->prev;
    7. tail->next = newnode;
    8. newnode->prev = tail;
    9. phead->prev = newnode;
    10. newnode->next = phead;
    11. }

    💊1.2.4双向链表的尾部删除

    双向链表的尾部删除的和尾部插入同样简单,因为它不需要遍历链表,只需要进行相应的链接操作即可。

    代码:

    1. //双向链表的尾部删除
    2. void LTPopBack(LTNode* phead)
    3. {
    4. assert(phead);
    5. assert(phead->next != phead);
    6. LTNode* tail = phead->prev;
    7. LTNode* tailprev = tail->prev;
    8. tailprev->next = phead;
    9. phead->prev = tailprev;
    10. free(tail);
    11. }

    💊1.2.5双向链表的头插

    链表的头插相对于单链表来说复杂程度不相上下,但也不难,因为有哨兵位节点的存在,在连接节点的时候还是相对方便的;

    代码:

    1. //双向链表的头插
    2. void LTPushFront(LTNode* phead, LTDataType x)
    3. {
    4. assert(phead);
    5. LTNode* newnode = BuyListNode(x);
    6. newnode->next = phead->next;
    7. phead->next->prev = newnode;
    8. phead->next = newnode;
    9. newnode->prev = phead;
    10. }

    💊1.2.6双向链表的数据的打印

    函数的实现非常简单,简单的遍历链表即可,唯一需要注意到的点就是我们什么时候停下来;

    代码:

    1. //双向链表的打印
    2. void LTPrint(LTNode* phead)
    3. {
    4. assert(phead);
    5. LTNode* cur = phead->next;
    6. while (cur != phead)
    7. {
    8. printf("%d ", cur->data);
    9. cur = cur->next;
    10. }
    11. printf("\n");
    12. }

    💊1.2.7双向链表的头部删除数据

    双向链表头部删除数据很简单,只需要将phead->next删除之后再将phead->next->next和phead链接起来即可;

    代码:

    1. void LTPopFront(LTNode* phead)
    2. {
    3. assert(phead);
    4. assert(phead->next != NULL);
    5. LTNode* first = phead->next;
    6. LTNode* second = first->next;
    7. free(first);
    8. phead->next = second;
    9. second->prev = phead;
    10. }

    💊1.2.8双向链表寻找数据

    方法和单链表是类似的,只是判断停止的条件不同;

    代码:

    1. //双向链表中寻找数据
    2. LTNode* LTFind(LTNode* phead, LTDataType x)
    3. {
    4. assert(phead);
    5. LTNode* cur = phead->next;
    6. while (cur != phead)
    7. {
    8. if (cur->data == x)
    9. {
    10. return cur;
    11. }
    12. cur = cur->next;
    13. }
    14. return NULL;
    15. }

    💊1.2.9在双向链表的任意位置插入数据

    这里我们实现的是在这个位置前进行插入数据,逻辑非常简单。

    分两个步骤:①申请空间;        ②链接节点;

    代码:

    1. //在双向链表的任意位置插入数据
    2. void LTInsert(LTNode* pos, LTDataType x)
    3. {
    4. assert(pos);
    5. LTNode* newnode = BuyListNode(x);
    6. LTNode* pre = pos->prev;
    7. pre->next = newnode;
    8. newnode->prev = pre;
    9. newnode->next = pos;
    10. pos->prev = newnode;
    11. }

    对此函数我们可以实现复用,重新构建头插和尾插

    1. //双向链表的头插
    2. void LTPushFront(LTNode* phead, LTDataType x)
    3. {
    4. assert(phead);
    5. LTInsert(phead->next, x);
    6. }
    7. //双向链表的尾插
    8. void LTPushBack(LTNode* phead, LTDataType x)
    9. {
    10. assert(phead);
    11. LTInsert(phead, x);
    12. }

    💊1.2.10在双向链表的任意位置删除数据

    这个函数我们实现的就是删除传入地址的节点,大的步骤也是分两个,先记录这个节点的前一个节点和后一个节点,然后free掉这个节点后,再链接;

    代码:

    1. //在双向链表的任意位置删除数据
    2. void LTErase(LTNode* pos)
    3. {
    4. assert(pos);
    5. LTNode* pre = pos->prev;
    6. LTNode* next = pos->next;
    7. free(pos);
    8. pre->next = next;
    9. next->prev = pre;
    10. }

    对此函数进行复用,改进头删和尾部删除函数:

    代码:

    1. //双向连边头部删除数据
    2. void LTPopFront(LTNode* phead)
    3. {
    4. assert(phead);
    5. LTErase(phead->next);
    6. }
    7. //双向链表的尾部删除
    8. void LTPopBack(LTNode* phead)
    9. {
    10. assert(phead);
    11. assert(phead->next != phead);
    12. LTErase(phead->prev);
    13. }

    代码汇总:

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. typedef int LTDataType;
    6. typedef struct ListNode
    7. {
    8. struct ListNode* next;
    9. struct ListNode* prev;
    10. LTDataType data;
    11. }LTNode;
    12. //初始化双向链表
    13. LTNode* InitList();
    14. //获取新的节点
    15. LTNode* BuyListNode(LTDataType x);
    16. //双向链表的尾插
    17. void LTPushBack(LTNode* phead, LTDataType x);
    18. //双向链表的尾部删除
    19. void LTPopBack(LTNode* phead);
    20. //双向链表的头插
    21. void LTPushFront(LTNode* phead, LTDataType x);
    22. //双向链表的打印
    23. void LTPrint(LTNode* phead);
    24. //双向连边头部删除数据
    25. void LTPopFront(LTNode* phead);
    26. //双向链表中寻找数据
    27. LTNode* LTFind(LTNode* phead, LTDataType x);
    28. //在双向链表的任意位置插入和删除数据
    29. void LTInsert(LTNode* pos, LTDataType x);
    30. void LTErase(LTNode* pos);
    31. //双向链表的判空
    32. bool LTEmpty(LTNode* phead);
    33. //双向链表大小的计算
    34. size_t LTSize(LTNode* phead);
    35. //双向链表的销毁
    36. void LTDestroy(LTNode* phead);
    37. #define _CRT_SECURE_NO_WARNINGS 1
    38. #include"List.h"
    39. LTNode* BuyListNode(LTDataType x)
    40. {
    41. LTNode* node = (LTNode*)malloc(sizeof(LTNode));
    42. if (node == NULL)
    43. {
    44. perror("malloc fail");
    45. exit(-1);
    46. }
    47. node->data = x;
    48. node->next = node->prev = NULL;
    49. return node;
    50. }
    51. //初始化双向链表
    52. LTNode* InitList()
    53. {
    54. LTNode* phead = BuyListNode(-1);
    55. phead->next = phead;
    56. phead->prev = phead;
    57. return phead;
    58. }
    59. //双向链表的尾插
    60. void LTPushBack(LTNode* phead, LTDataType x)
    61. {
    62. assert(phead);
    63. //LTNode* newnode = BuyListNode(x);
    64. //LTNode* tail = phead->prev;
    65. //tail->next = newnode;
    66. //newnode->prev = tail;
    67. //phead->prev = newnode;
    68. //newnode->next = phead;
    69. LTInsert(phead, x);
    70. }
    71. //双向链表的尾部删除
    72. void LTPopBack(LTNode* phead)
    73. {
    74. assert(phead);
    75. assert(phead->next != phead);
    76. //LTNode* tail = phead->prev;
    77. //LTNode* tailprev = tail->prev;
    78. //tailprev->next = phead;
    79. //phead->prev = tailprev;
    80. //free(tail);
    81. LTErase(phead->prev);
    82. }
    83. //双向链表的头插
    84. void LTPushFront(LTNode* phead, LTDataType x)
    85. {
    86. assert(phead);
    87. //LTNode* newnode = BuyListNode(x);
    88. //newnode->next = phead->next;
    89. //phead->next->prev = newnode;
    90. //phead->next = newnode;
    91. //newnode->prev = phead;
    92. LTInsert(phead->next, x);
    93. }
    94. //双向链表的打印
    95. void LTPrint(LTNode* phead)
    96. {
    97. assert(phead);
    98. LTNode* cur = phead->next;
    99. while (cur != phead)
    100. {
    101. printf("%d ", cur->data);
    102. cur = cur->next;
    103. }
    104. printf("\n");
    105. }
    106. //双向连边头部删除数据
    107. void LTPopFront(LTNode* phead)
    108. {
    109. assert(phead);
    110. //assert(phead->next != NULL);
    111. //LTNode* first = phead->next;
    112. //LTNode* second = first->next;
    113. //free(first);
    114. //phead->next = second;
    115. //second->prev = phead;
    116. LTErase(phead->next);
    117. }
    118. //双向链表中寻找数据
    119. LTNode* LTFind(LTNode* phead, LTDataType x)
    120. {
    121. assert(phead);
    122. LTNode* cur = phead->next;
    123. while (cur != phead)
    124. {
    125. if (cur->data == x)
    126. {
    127. return cur;
    128. }
    129. cur = cur->next;
    130. }
    131. return NULL;
    132. }
    133. //在双向链表的任意位置插入和删除数据
    134. void LTInsert(LTNode* pos, LTDataType x)
    135. {
    136. assert(pos);
    137. LTNode* newnode = BuyListNode(x);
    138. LTNode* pre = pos->prev;
    139. pre->next = newnode;
    140. newnode->prev = pre;
    141. newnode->next = pos;
    142. pos->prev = newnode;
    143. }
    144. void LTErase(LTNode* pos)
    145. {
    146. assert(pos);
    147. LTNode* pre = pos->prev;
    148. LTNode* next = pos->next;
    149. free(pos);
    150. pre->next = next;
    151. next->prev = pre;
    152. }
    153. //双向链表的判空
    154. bool LTEmpty(LTNode* phead)
    155. {
    156. assert(phead);
    157. return phead->next != phead;
    158. }
    159. //双向链表大小的计算
    160. size_t LTSize(LTNode* phead)
    161. {
    162. assert(phead);
    163. size_t x = 0;
    164. LTNode* cur = phead->next;
    165. while (cur != phead)
    166. {
    167. x++;
    168. cur = cur->next;
    169. }
    170. return x;
    171. }
    172. //双向链表的销毁
    173. void LTDestroy(LTNode* phead)
    174. {
    175. assert(phead);
    176. LTNode* cur = phead->next;
    177. while (cur != phead)
    178. {
    179. LTNode* next = cur->next;
    180. free(cur);
    181. cur = next;
    182. }
    183. free(phead);
    184. }
    185. #define _CRT_SECURE_NO_WARNINGS 1
    186. #include"List.h"
    187. int main()
    188. {
    189. LTNode* phead = InitList();
    190. LTPushBack(phead, 1);
    191. LTPushBack(phead, 2);
    192. LTPushBack(phead, 3);
    193. LTPushBack(phead, 4);
    194. LTPushBack(phead, 5);
    195. LTPrint(phead);
    196. LTPrint(phead);
    197. LTNode* x = LTFind(phead, 3);
    198. if (x)
    199. {
    200. x->data = 100;
    201. LTPrint(phead);
    202. }
    203. LTPopBack(phead);
    204. LTPrint(phead);
    205. printf("%d\n", LTSize(phead));
    206. LTDestroy(phead);
    207. return 0;
    208. }

    以上就是双向循环链表表的实现和各个函数需要注意的细节,如果有错误可以在评论区指正! 

     

  • 相关阅读:
    glibc: dup/dup2/dup3/F_DUPFD
    3.1 Android eBPF代码仓解读
    《Python编程无师自通》读书笔记
    EM@平面直线方程和直线位置关系判定条件
    Kotlin(十一)Kotlin中的Object关键字
    在内核调试时输出调试信息
    Win10删除文件需要TrustedInstaller权限的解决方法
    JavaScript Web APIs第六天笔记
    网络编程-套接字相关基础知识
    C++中的CopyElision
  • 原文地址:https://blog.csdn.net/weixin_61766635/article/details/127716053