首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

【数据结构 · 初阶】- 带头双向循环链表

  • 25-04-24 12:21
  • 4165
  • 6560
blog.csdn.net

目录

1.尾插

2.初始化

3.尾删、头插、头删

4.查找,返回 pos 指针

5.pos 前插入

优化头插,直接复用

优化尾插,直接复用

6.pos 位删除

头删尾删简化

7.销毁

整体代码

List.h

List.c

Test.c


循环:1.尾 next 指向哨兵位的头。2.哨兵位的头的 prev 指向尾

基本结构:

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

1.尾插

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

为什么要 assert ?
这里的 phead 是结构体指针,存放的是 plist 的值。plist 指向 malloc 的新节点。
malloc 的新节点的地址一定不为空。如果为空,就是传错了,所以要 assert

为什么不用二级指针?
改变的都是结构体的变量,用结构体指针足矣。这也是哨兵位的优势所在。

无头单向不循环(单链表)里面,要遍例来找尾;还要判断链表是否为空,如果为空,先赋值
这里就不用刻意找尾;且可以兼容空的情况,方便很多。下面是单链表的尾插

  1. void SLTPushBack(SLTNode** pphead, SLTDataType x)
  2. {
  3. assert(pphead);
  4. SLTNode* newnode = BuySLTNode(x);
  5. if (*pphead == NULL)
  6. {
  7. *pphead = newnode;
  8. }
  9. else
  10. {
  11. // 找尾
  12. SLTNode* tail = *pphead;
  13. while (tail->next != NULL)
  14. {
  15. tail = tail->next;
  16. }
  17. tail->next = newnode;
  18. }
  19. }

2.初始化

目标:malloc 一个哨兵位的头节点,让 plist 指向哨兵位头节点
现象:要将头节点的地址传给 plist ,会改变 plist 的值
结论:要用二级指针,不能用一级指针

  1. LTNode* plist = NULL;
  2. LTInit(plist);
  3. void LTInit(LTNode** pphead);

后面的插入,删除都是改变结构体成员,用一级指针,只有这里用二级指针。
还有更好的方式:OJ 题中普遍用

List.c

  1. LTNode* BuyListNode(LTDataType x)
  2. {
  3. LTNode* node = (LTNode*)malloc(sizeof(LTNode));
  4. if (node == NULL)
  5. {
  6. perror("malloc fail");
  7. return NULL;
  8. }
  9. node->data = x;
  10. node->next = NULL;
  11. node->prev = NULL;
  12. return node;
  13. }
  14. LTNode* LTInit()
  15. {
  16. LTNode* phead = BuyListNode(-1);
  17. phead->next = phead;
  18. phead->prev = phead;
  19. return phead;
  20. }
  21. void LTPrint(LTNode* phead)
  22. {
  23. assert(phead);
  24. LTNode* cur = phead->next;
  25. printf("<=head=>");
  26. while (cur != phead)
  27. {
  28. printf("%d<=>", cur->data);
  29. cur = cur->next;
  30. }
  31. printf("\n");
  32. }
  33. void LTPushBack(LTNode* phead, LTDataType x)
  34. {
  35. assert(phead);
  36. LTNode* newnode = BuyListNode(x);
  37. LTNode* tail = phead->prev;
  38. //phead tail newnode
  39. tail->next = newnode;
  40. newnode->prev = tail;
  41. phead->prev = newnode;
  42. newnode->next = phead;
  43. }

Test.c

  1. void ListTest1()
  2. {
  3. LTNode* plist = LTInit();
  4. LTPushBack(plist, 1);
  5. LTPushBack(plist, 2);
  6. LTPushBack(plist, 3);
  7. LTPushBack(plist, 4);
  8. LTPrint(plist);
  9. }

3.尾删、头插、头删

空链表不能删(只有哨兵位的头节点),所以要判断链表是否为空

  1. bool LTEmpty(LTNode* phead)
  2. {
  3. assert(phead);
  4. /*
  5. if (phead->next == phead)
  6. {
  7. return true;
  8. }
  9. else
  10. {
  11. return false;
  12. }
  13. */
  14. return phead->next == phead;
  15. }

尾删

  1. void LTPopBack(LTNode* phead)
  2. {
  3. assert(phead);
  4. assert(!LTEmpty(phead));
  5. LTNode* tail = phead->prev;
  6. LTNode* tailPrev = tail->prev;
  7. tailPrev->next = phead;
  8. phead->prev = tailPrev;
  9. free(tail);
  10. tail = NULL;
  11. }

头插

(1)2个指针的错误写法

  1. void LTPushFront(LTNode* phead, LTDataType x)
  2. {
  3. assert(phead);
  4. LTNode* newnode = BuyListNode(x);
  5. phead->next = newnode;
  6. newnode->prev = phead;
  7. ......
  8. }

如果先搞这两步,就不能轻易找到原来的第一个了

(2)2个指针的正确写法

一定先处理离的远的那一边

  1. void LTPushFront(LTNode* phead, LTDataType x)
  2. {
  3. assert(phead);
  4. LTNode* newnode = BuyListNode(x);
  5. phead->next->prev = newnode;
  6. newnode->next = phead->next;
  7. phead->next = newnode;
  8. newnode->prev = phead;
  9. }

(3)3个指针,顺序随便,效率更高

  1. void LTPushFront(LTNode* phead, LTDataType x)
  2. {
  3. assert(phead);
  4. LTNode* newnode = BuyListNode(x);
  5. LTNode* first = phead->next; // 第三个指针
  6. first->prev = newnode;
  7. newnode->next = first;
  8. phead->next = newnode;
  9. newnode->prev = phead
  10. }

头删

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

4.查找,返回 pos 指针

  1. LTNode* LTFind(LTNode* phead, LTDataType x); // List.h
  2. LTNode* LTFind(LTNode* phead, LTDataType x) // List.c
  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. }
  16. void ListTest2() // Test.c
  17. {
  18. LTNode* plist = LTInit();
  19. ......
  20. LTNode* pos = LTFind(plist, 2);
  21. }

5.pos 前插入

要配合查找使用
单链表要遍例找前一个,现在不需要这么麻烦

要用2个指针,先动离的远的,即 pos->prev 和 newnode 的链接
为高效,我们用3个指针

  1. void LTInsert(LTNode* pos, LTDataType x)
  2. {
  3. assert(pos);
  4. LTNode* newnode = BuyListNode(x);
  5. LTNode* prev = pos->prev; // 第3个指针
  6. prev->next = newnode;
  7. newnode->prev = prev;
  8. newnode->next = pos;
  9. pos->prev = newnode;
  10. }

优化头插,直接复用

  1. void LTPushFront(LTNode* phead, LTDataType x)
  2. {
  3. assert(phead);
  4. LTInsert(phead->next, x);
  5. }

优化尾插,直接复用

  1. void LTPushBack(LTNode* phead, LTDataType x)
  2. {
  3. assert(phead);
  4. LTInsert(phead, x);
  5. }

 遍例是从 LTNode* cur = phead->next ;开始。从逻辑上就是尾插

6.pos 位删除

配合查找使用

  1. void LTErase(LTNode* pos)
  2. {
  3. assert(pos);
  4. LTNode* p = pos->prev;
  5. LTNode* n = pos->next;
  6. p->next = n;
  7. n->prev = p;
  8. free(pos);
  9. //pos = NULL;
  10. }

pos置空没用,形参改变不影响实参。
为保持接口风格一致,没有必要用二级指针,通常由使用的人在外面置空

里面变如果改变外面的话,一定有 解引用 操作

  1. void ListTest2()
  2. {
  3. LTNode* plist = LTInit();
  4. ......
  5. LTNode* pos = LTFind(plist, 2);
  6. if (pos)
  7. {
  8. LTErase(pos);
  9. pos = NULL;
  10. }
  11. LTPrint(plist);
  12. }

LTErase 肯定不能删哨兵位的头节点
但 C语言中不好检查,所以我们暂且不做处理。删哨兵程序会挂
C++的结构好检查

头删尾删简化

  1. void LTPopBack(LTNode* phead)
  2. {
  3. assert(phead);
  4. LTErase(phead->prev);
  5. }
  6. void LTPopFront(LTNode* phead)
  7. {
  8. assert(phead);
  9. LTErase(phead->next);
  10. }

7.销毁

  1. void LTDestroy(LTNode* phead)
  2. {
  3. assert(phead);
  4. LTNode* cur = phead->next;
  5. LTNode* next = cur->next;
  6. while (cur != phead)
  7. {
  8. free(cur);
  9. cur = next;
  10. next = next->next;
  11. }
  12. free(phead);
  13. //phead = NULL;
  14. }
  15. void ListTest2()
  16. {
  17. LTNode* plist = LTInit();
  18. ......
  19. LTDestroy(plist);
  20. plist = NULL;
  21. }

整体代码

List.h

  1. #pragma once
  2. #include
  3. #include
  4. #include
  5. #include
  6. typedef int LTDataType;
  7. typedef struct ListNode
  8. {
  9. struct ListNode* prev;
  10. struct ListNode* next;
  11. LTDataType data;
  12. }LTNode;
  13. LTNode* LTInit();
  14. void LTPrint(LTNode* phead);
  15. bool LTEmpty(LTNode* phead); // 判断链表是否为空
  16. void LTPushBack(LTNode* phead, LTDataType x);
  17. void LTPopBack(LTNode* phead);
  18. void LTPushFront(LTNode* phead, LTDataType x);
  19. void LTPopFront(LTNode* phead);
  20. LTNode* LTFind(LTNode* phead, LTDataType x);
  21. void LTInsert(LTNode* pos, LTDataType x); // pos前插入
  22. void LTErase(LTNode* pos); // pos位删除
  23. void LTDestroy(LTNode* phead);

List.c

  1. #include "List.h"
  2. LTNode* BuyListNode(LTDataType x)
  3. {
  4. LTNode* node = (LTNode*)malloc(sizeof(LTNode));
  5. if (node == NULL)
  6. {
  7. perror("malloc fail");
  8. return NULL;
  9. }
  10. node->data = x;
  11. node->next = NULL;
  12. node->prev = NULL;
  13. return node;
  14. }
  15. LTNode* LTInit()
  16. {
  17. LTNode* phead = BuyListNode(-1);
  18. phead->next = phead;
  19. phead->prev = phead;
  20. return phead;
  21. }
  22. void LTPrint(LTNode* phead)
  23. {
  24. assert(phead);
  25. LTNode* cur = phead->next;
  26. printf("<=head=>");
  27. while (cur != phead)
  28. {
  29. printf("%d<=>", cur->data);
  30. cur = cur->next;
  31. }
  32. printf("\n");
  33. }
  34. bool LTEmpty(LTNode* phead)
  35. {
  36. assert(phead);
  37. /*
  38. if (phead->next == phead)
  39. {
  40. return true;
  41. }
  42. else
  43. {
  44. return false;
  45. }
  46. */
  47. return phead->next == phead;
  48. }
  49. void LTPushBack(LTNode* phead, LTDataType x)
  50. {
  51. assert(phead);
  52. /*
  53. LTNode* newnode = BuyListNode(x);
  54. LTNode* tail = phead->prev;
  55. //phead tail newnode
  56. tail->next = newnode;
  57. newnode->prev = tail;
  58. phead->prev = newnode;
  59. newnode->next = phead;
  60. */
  61. LTInsert(phead, x);
  62. }
  63. void LTPopBack(LTNode* phead)
  64. {
  65. assert(phead);
  66. /*
  67. assert(!LTEmpty(phead));
  68. LTNode* tail = phead->prev;
  69. LTNode* tailPrev = tail->prev;
  70. tailPrev->next = phead;
  71. phead->prev = tailPrev;
  72. free(tail);
  73. tail = NULL;
  74. */
  75. LTErase(phead->prev);
  76. }
  77. void LTPushFront(LTNode* phead, LTDataType x)
  78. {
  79. assert(phead);
  80. /*
  81. LTNode* newnode = BuyListNode(x);
  82. LTNode* first = phead->next;
  83. first->prev = newnode;
  84. newnode->next = first;
  85. phead->next = newnode;
  86. newnode->prev = phead
  87. */
  88. LTInsert(phead->next, x);
  89. }
  90. //void LTPushFront(LTNode* phead, LTDataType x)
  91. //{
  92. // assert(phead);
  93. // LTNode* newnode = BuyListNode(x);
  94. // // 先
  95. // phead->next->prev = newnode;
  96. // newnode->next = phead->next;
  97. // // 后
  98. // phead->next = newnode;
  99. // newnode->prev = phead;
  100. //}
  101. void LTPopFront(LTNode* phead)
  102. {
  103. assert(phead);
  104. /*
  105. assert(!LTEmpty(phead));
  106. LTNode* first = phead->next->next;
  107. LTNode* del = phead->next;
  108. phead->next = first;
  109. first->prev = phead;
  110. free(del);
  111. del = NULL;
  112. */
  113. LTErase(phead->next);
  114. }
  115. LTNode* LTFind(LTNode* phead, LTDataType x)
  116. {
  117. assert(phead);
  118. LTNode* cur = phead->next;
  119. while (cur != phead)
  120. {
  121. if (cur->data == x)
  122. {
  123. return cur;
  124. }
  125. cur = cur->next;
  126. }
  127. return NULL;
  128. }
  129. void LTInsert(LTNode* pos, LTDataType x)
  130. {
  131. assert(pos);
  132. LTNode* newnode = BuyListNode(x);
  133. LTNode* prev = pos->prev;
  134. prev->next = newnode;
  135. newnode->prev = prev;
  136. newnode->next = pos;
  137. pos->prev = newnode;
  138. }
  139. void LTErase(LTNode* pos)
  140. {
  141. assert(pos);
  142. LTNode* p = pos->prev;
  143. LTNode* n = pos->next;
  144. p->next = n;
  145. n->prev = p;
  146. free(pos);
  147. //pos = NULL;
  148. }
  149. void LTDestroy(LTNode* phead)
  150. {
  151. assert(phead);
  152. LTNode* cur = phead->next;
  153. LTNode* next = cur->next;
  154. while (cur != phead)
  155. {
  156. free(cur);
  157. cur = next;
  158. next = next->next;
  159. }
  160. free(phead);
  161. //phead = NULL;
  162. }

Test.c

  1. #include "List.h"
  2. void ListTest1()
  3. {
  4. LTNode* plist = LTInit();
  5. LTPushBack(plist, 1);
  6. LTPushBack(plist, 2);
  7. LTPushBack(plist, 3);
  8. LTPushBack(plist, 4);
  9. LTPrint(plist);
  10. LTPopBack(plist);
  11. LTPopBack(plist);
  12. LTPopBack(plist);
  13. LTPopBack(plist);
  14. LTPrint(plist);
  15. LTPushFront(plist, 1);
  16. LTPushFront(plist, 2);
  17. LTPushFront(plist, 3);
  18. LTPushFront(plist, 4);
  19. LTPrint(plist);
  20. LTPopFront(plist);
  21. LTPopFront(plist);
  22. LTPrint(plist);
  23. }
  24. void ListTest2()
  25. {
  26. LTNode* plist = LTInit();
  27. LTPushBack(plist, 1);
  28. LTPushBack(plist, 2);
  29. LTPushBack(plist, 3);
  30. LTPushBack(plist, 4);
  31. LTPrint(plist);
  32. LTNode* pos = LTFind(plist, 2);
  33. LTInsert(pos, 9);
  34. LTPrint(plist);
  35. if (pos)
  36. {
  37. LTErase(pos);
  38. pos = NULL;
  39. }
  40. LTPrint(plist);
  41. LTDestroy(plist);
  42. plist = NULL;
  43. }
  44. int main()
  45. {
  46. ListTest2();
  47. return 0;
  48. }

本篇的分享就到这里了,感谢观看,如果对你有帮助,别忘了点赞+收藏+关注。
小编会以自己学习过程中遇到的问题为素材,持续为您推送文章

注:本文转载自blog.csdn.net的祁同伟.的文章"https://blog.csdn.net/HDSTQTW/article/details/147255828"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

122
操作系统
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top