• 数据结构——双链表


    fe594ea5bf754ddbb223a54d8fb1e7bc.gif

    目录

    ​一.前言

    二.双链表的基本结构

    三.准备阶段

    3.1 空间函数

    3.2 初始化函数

    3.3 打印函数

    四.链表的实现

    4.1 尾插函数(可以让我们直观认识到该结构的妙用)

    4.2 尾删函数

    4.3 头插函数

    4.4 头删函数

    4.5 pos之前插入x函数

    4.6 在pos位置删除函数

    4.7 查找函数 

    4.8 销毁函数

    五.全部代码

    ​六.结语


    8fb442646f144d8daecdd2b61ec78ecd.png一.前言

    如果有友友看了我上一篇文章:数据结构——单链表,那么本篇的双链表会让你感到非常的安逸~无压力学会。码字不易,希望大家多多支持我呀!(三连+关注,你是我滴神!)

    二.双链表的基本结构

     两两组合下链表一共有八种结构:而我们本文的重点就是带头双向循环链表。

    虽然有这么多的链表结构,但我们在实际中用得最多的还是以下两种:

    • 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
    • 带头双向循环链表:结构最复杂,一般用在单独存储数据结构。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们用代码实现了就知道了。

    三.准备阶段

    3.1 空间函数

    1. //空间函数
    2. LTNode* BuyLTNode(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 = NULL;
    12. node->prev = NULL;
    13. return node;
    14. }

    3.2 初始化函数

    这里得用到二级指针了,要想改变plist(LTNode*)类型的指针,初始化函数就得用到(LTNode**)去接收,但我们可以换个思路,既然我们在不用到二级指针的情况下phead只是plist的临时拷贝,那么我们也可以让初始化函数的返回类型发生变化,这样也可以达到改变plist的作用。把初始化函数赋值给plist即可。

    1. //初始化函数
    2. LTNode* LTInit()
    3. {
    4. //创造一个新节点,该节点是双向循环节点
    5. LTNode*phead = BuyLTNode(-1);
    6. phead->next = phead;
    7. phead->prev = phead;
    8. return phead;
    9. //最后只要把这个我们人工初始化好的节点赋值给plist就好了
    10. }

    3.3 打印函数

    cur指向哨兵位phead的后面一位节点,这样方便打印。

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

    四.链表的实现

    先来个开胃小菜:

    4.1 尾插函数(可以让我们直观认识到该结构的妙用)

    不同于单链表(有节点要找尾,没节点插入第一个),在双链表中只需要把4个指针都标明好就行,方便又易懂,不用担心是否为空链表。最后出了作用域tail和newnode就会销毁,而新节点是由malloc在堆上申请的,所以还在。

    另外还有一点,这样不需要用到二级指针因为我们有带头节点,这样phead是永远指向它的。

    1. void LTPushBack(LTNode* phead, LTDataType x)
    2. {
    3. assert(phead);//这里肯定要断言一下的,因为就算是初始化也会有作为头部的哨兵位。
    4. LTNode* newnode = BuyLTNode(x);
    5. LTNode* tail = phead->prev;
    6. tail->next = newnode;
    7. newnode->prev = tail;
    8. newnode->next = phead;
    9. phead->prev = newnode;
    10. }

    4.2 尾删函数

       要实现尾插我们得注意两点:

    • 找到指向尾节点前一个节点的指针
    • 单链表为空时不能再进行尾删
    1. //尾删函数
    2. void LTPopBack(LTNode* phead)
    3. {
    4. assert(phead);//避免phead初始化没完成而导致出错
    5. assert(phead->next!=phead);//当删除到没有节点时,不能再继续尾删
    6. LTNode* tail = phead->prev;
    7. LTNode* tailPrev = tail->prev;
    8. free(tail);
    9. tailPrev->next = phead;
    10. phead->prev = tailPrev;
    11. }

    大家应该逐渐发现双链表的优势所在了吧,基本上不用像单链表一样考虑到特殊情况然后去修改代码,单链表需要条件判断的问题,双链表都可以应对~

    4.3 头插函数

    头插也不需要用到二级指针,因为我们要改变的并非phead本身(phead只需要指向哨兵就行),而是改变phead指向的结构体(改变哨兵节点的指向)。

    注意别这么改,虽然最后可以通过各种前驱指针找到d1,但代价太大了。

    所以我们需要标记首节点(d1)的指向,让每次头插时都能找到原先作为头节点的指针。这里需要注意的是有先后顺序,先让新节点与首节点d1(phead->next指向)相互链接再让它与哨兵节点链接,这样可以避免d1节点地址的丢失。

    1. //头插函数
    2. void PushFront(LTNode* phead, LTDataType x)
    3. {
    4. assert(phead);
    5. LTNode* newnode = BuyLTNode(x);
    6. newnode->next = phead->next;
    7. phead->next->prev = newnode;
    8. phead->next = newnode;
    9. newnode->prev = phead;
    10. }

    测试一下:

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

    但我建议换另一种写法,上面这种写法容易出错。

    定义好一个first的指针,这样顺序怎么搞都不会玩脱。

    1. //头插函数
    2. void LTPushFront(LTNode* phead, LTDataType x)
    3. {
    4. assert(phead);
    5. LTNode* newnode = BuyLTNode(x);
    6. LTNode* first = phead->next;
    7. first->prev = newnode;
    8. newnode->next = first;
    9. phead->next = newnode;
    10. newnode->prev = newnode;
    11. }

    4.4 头删函数

    上图是基本思路,我们定义指针的时候不要吝啬,出手阔气一点系统不会在意这点内存滴~

    就算是只剩下一个节点,照样拿捏~

    测试一下:

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

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

    4.5 pos之前插入x函数

    简简单单~

    1. //pos之前插入x
    2. void LTInsert(LTNode* phead,LTNode* pos, LTDataType x)
    3. {
    4. assert(phead);
    5. LTNode* newnode = BuyLTNode(x);
    6. LTNode* posPrev = pos->prev;
    7. newnode->prev = posPrev;
    8. newnode->next = pos;
    9. pos->prev = newnode;
    10. posPrev->next = newnode;
    11. }

    其实我们可以发现写好了Insert函数再搭配的Find函数(更直观)就相当于写好了头插尾插了~

    4.6 在pos位置删除函数

    1. //删除pos位置的值
    2. void LTErase(LTNode* pos)
    3. {
    4. assert(pos);
    5. LTNode* posPrev = pos->prev;
    6. LTNode* posNext = pos->next;
    7. free(pos);
    8. posPrev->next = posNext;
    9. posNext->prev = posPrev;
    10. }

    该函数也可以复用,起到头删,尾删的作用~

    4.7 查找函数 

    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. else
    13. {
    14. cur = cur->next;
    15. }
    16. }
    17. return NULL;
    18. }

    可以与Insert函数搭配使用~

    1. void TestList4()
    2. {
    3. LTNode* plist = LTInit();
    4. LTPushBack(plist, 1);
    5. LTPushBack(plist, 2);
    6. LTPushBack(plist, 3);
    7. LTPushBack(plist, 4);
    8. LTPushBack(plist, 5);
    9. LTPopFront(plist);
    10. LTPrint(plist);
    11. int x = 0;
    12. scanf("%d", &x);
    13. LTNode* pos = LTFind(plist, x);
    14. if (pos)
    15. {
    16. LTInsert(plist, pos, 6);
    17. }
    18. LTPrint(plist);
    19. }

    4.8 销毁函数

    1. //销毁函数
    2. void LTDestort(LTNode* phead)
    3. {
    4. assert(phead);
    5. LTNode* cur = phead->next;
    6. while (cur != phead)
    7. {
    8. LTNode* next = cur->next;
    9. free(cur);
    10. cur = next;
    11. }
    12. free(phead);
    13. }

    不需要置空 ,里面的形参不会影响到外面的实参。既然里面不用置空,那我们就让外面用的置空就行了。

    1. void TestList4()
    2. {
    3. LTNode* plist = LTInit();
    4. LTPushBack(plist, 1);
    5. LTPushBack(plist, 2);
    6. LTPushBack(plist, 3);
    7. LTPushBack(plist, 4);
    8. LTPushBack(plist, 5);
    9. LTPopFront(plist);
    10. LTPrint(plist);
    11. int x = 0;
    12. scanf("%d", &x);
    13. LTNode* pos = LTFind(plist, x);
    14. if (pos)
    15. {
    16. LTInsert(plist, pos, 6);
    17. }
    18. LTPrint(plist);
    19. LTDestory(plist);
    20. plist = NULL;
    21. }

    五.全部代码

     

    1. //List.h
    2. #pragma once
    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. //空间函数
    14. LTNode* BuyLTNode(LTDataType x);
    15. //初始化函数、
    16. LTNode* LTInit();
    17. //打印函数
    18. void LTPrint(LTNode* phead);
    19. //尾插函数
    20. void LTPushBack(LTNode* phead, LTDataType x);
    21. //尾删函数
    22. void LTPopBack(LTNode* phead);
    23. //头插函数
    24. void LTPushFront(LTNode* phead, LTDataType x);
    25. //头删函数
    26. void LTPopFront(LTNode* phead);
    27. //寻找函数
    28. LTNode* LTFind(LTNode* phead, LTDataType x);
    29. //pos之前插入x
    30. void LTInsert(LTNode* phead,LTNode* pos, LTDataType x);
    31. //删除pos位置的值
    32. void LTErase(LTNode* pos);
    33. //销毁函数
    34. void LTDestort(LTNode* phead);

    ————————————————————————————————————————

    1. //List.c
    2. #define _CRT_SECURE_NO_WARNINGS 1
    3. #include "List.h"
    4. //空间函数
    5. LTNode* BuyLTNode(LTDataType x)
    6. {
    7. LTNode* node = (LTNode*)malloc(sizeof(LTNode));
    8. if (node == NULL)
    9. {
    10. perror("malloc fail");
    11. exit(-1);
    12. }
    13. node->data = x;
    14. node->next = NULL;
    15. node->prev = NULL;
    16. return node;
    17. }
    18. //初始化函数
    19. LTNode* LTInit()
    20. {
    21. LTNode* phead = BuyLTNode(-1);
    22. phead->next = phead;
    23. phead->prev = phead;
    24. return phead;
    25. }
    26. //打印函数
    27. void LTPrint(LTNode* phead)
    28. {
    29. assert(phead);
    30. printf("phead<=>");
    31. LTNode* cur = phead->next;
    32. while (cur != phead)
    33. {
    34. printf("%d<=>", cur->data);
    35. cur = cur->next;
    36. }
    37. printf("\n");
    38. }
    39. //尾插函数
    40. void LTPushBack(LTNode* phead, LTDataType x)
    41. {
    42. assert(phead);//这里肯定要断言一下的,因为就算是初始化也会有作为头部的哨兵位。
    43. LTNode* newnode = BuyLTNode(x);
    44. LTNode* tail = phead->prev;
    45. tail->next = newnode;
    46. newnode->prev = tail;
    47. newnode->next = phead;
    48. phead->prev = newnode;
    49. }
    50. //尾删函数
    51. void LTPopBack(LTNode* phead)
    52. {
    53. assert(phead);
    54. assert(phead->next != phead);
    55. LTNode* tail = phead->prev;
    56. LTNode* tailPrev = tail->prev;
    57. free(tail);
    58. tailPrev->next = phead;
    59. phead->prev = tailPrev;
    60. }
    61. 头插函数
    62. //void LTPushFront(LTNode* phead, LTDataType x)
    63. //{
    64. // assert(phead);
    65. // LTNode* newnode = BuyLTNode(x);
    66. // newnode->next = phead->next;
    67. // phead->next->prev = newnode;
    68. // phead->next = newnode;
    69. // newnode->prev = phead;
    70. //}
    71. //头插函数
    72. void LTPushFront(LTNode* phead, LTDataType x)
    73. {
    74. assert(phead);
    75. LTNode* newnode = BuyLTNode(x);
    76. LTNode* first = phead->next;
    77. first->prev = newnode;
    78. newnode->next = first;
    79. phead->next = newnode;
    80. newnode->prev = newnode;
    81. }
    82. //头删函数
    83. void LTPopFront(LTNode* phead)
    84. {
    85. assert(phead);
    86. assert(phead->next != phead);
    87. LTNode* first = phead->next;
    88. LTNode* second = first->next;
    89. phead->next = second;
    90. second->prev = phead;
    91. free(first);
    92. }
    93. //寻找函数
    94. LTNode* LTFind(LTNode* phead, LTDataType x)
    95. {
    96. assert(phead);
    97. LTNode* cur = phead->next;
    98. while (cur!=phead)
    99. {
    100. if (cur->data == x)
    101. {
    102. return cur;
    103. }
    104. else
    105. {
    106. cur = cur->next;
    107. }
    108. }
    109. return NULL;
    110. }
    111. //pos之前插入x
    112. void LTInsert(LTNode* phead,LTNode* pos, LTDataType x)
    113. {
    114. assert(phead);
    115. LTNode* newnode = BuyLTNode(x);
    116. LTNode* posPrev = pos->prev;
    117. newnode->prev = posPrev;
    118. newnode->next = pos;
    119. pos->prev = newnode;
    120. posPrev->next = newnode;
    121. }
    122. //删除pos位置的值
    123. void LTErase(LTNode* pos)
    124. {
    125. assert(pos);
    126. LTNode* posPrev = pos->prev;
    127. LTNode* posNext = pos->next;
    128. free(pos);
    129. posPrev->next = posNext;
    130. posNext->prev = posPrev;
    131. }
    132. //销毁函数
    133. void LTDestort(LTNode* phead)
    134. {
    135. assert(phead);
    136. LTNode* cur = phead->next;
    137. while (cur != phead)
    138. {
    139. LTNode* next = cur->next;
    140. free(cur);
    141. cur = next;
    142. }
    143. free(phead);
    144. }

    ————————————————————————————————————————

    1. //Test.c
    2. #define _CRT_SECURE_NO_WARNINGS 1
    3. #include "List.h"
    4. void TestList1()
    5. {
    6. LTNode* plist = LTInit();
    7. LTPushBack(plist, 1);
    8. LTPushBack(plist, 2);
    9. LTPushBack(plist, 3);
    10. LTPushBack(plist, 4);
    11. LTPushBack(plist, 5);
    12. LTPopBack(plist);
    13. LTPrint(plist);
    14. LTPopBack(plist);
    15. LTPrint(plist);
    16. LTPopBack(plist);
    17. LTPrint(plist);
    18. LTPopBack(plist);
    19. LTPrint(plist);
    20. LTPopBack(plist);
    21. LTPrint(plist);
    22. }
    23. void TestList2()
    24. {
    25. LTNode* plist = LTInit();
    26. LTPushBack(plist, 1);
    27. LTPushBack(plist, 2);
    28. LTPushBack(plist, 3);
    29. LTPushBack(plist, 4);
    30. LTPushBack(plist, 5);
    31. LTPushFront(plist, 10);
    32. LTPrint(plist);
    33. }
    34. void TestList3()
    35. {
    36. LTNode* plist = LTInit();
    37. LTPushBack(plist, 1);
    38. LTPushBack(plist, 2);
    39. LTPushBack(plist, 3);
    40. LTPushBack(plist, 4);
    41. LTPushBack(plist, 5);
    42. LTPopFront(plist);
    43. LTPrint(plist);
    44. }
    45. void TestList4()
    46. {
    47. LTNode* plist = LTInit();
    48. LTPushBack(plist, 1);
    49. LTPushBack(plist, 2);
    50. LTPushBack(plist, 3);
    51. LTPushBack(plist, 4);
    52. LTPushBack(plist, 5);
    53. LTPopFront(plist);
    54. LTPrint(plist);
    55. int x = 0;
    56. scanf("%d", &x);
    57. LTNode* pos = LTFind(plist, x);
    58. if (pos)
    59. {
    60. LTInsert(plist, pos, 6);
    61. }
    62. LTPrint(plist);
    63. }
    64. int main()
    65. {
    66. //TestList1();
    67. //TestList2();
    68. //TestList3();
    69. TestList4();
    70. return 0;
    71. }

    4b12323f94834afd9ec146a3c10df229.jpeg六.结语

    双链表是不是非常轻松呢~不像我们学单链表那时候草木皆兵,啥都要判断一下,双链表突出的就是一个结构稳定,安逸的很~最后感谢大家的观看,友友们能够学习到新的知识是额滴荣幸,期待我们下次相见~

  • 相关阅读:
    spark集群搭建
    沐神深度学习报错 can only concatenate str (not “int“) to str
    一、Vue3基础[组件(props、事件、插槽)]
    力扣练习——48 找到小镇的法官
    全新架构升级,游戏表现超过锐龙9, 英特尔正式发布12代酷睿
    LeetCode:第305场周赛【总结】
    vuex五大核心、辅助函数
    vue2 在循环里,给字体加上随机颜色并加上随机图标且少重复
    selenium进阶设置
    RIS 系列 Beyond One-to-One: Rethinking the Referring Image Segmentation 论文阅读笔记
  • 原文地址:https://blog.csdn.net/fax_player/article/details/133187232