• 手撕双链表


    > 作者简介:დ旧言~,目前大一,现在学习Java,c,c++,Python等
    > 座右铭:松树千年终是朽,槿花一日自为荣。
    > 望小伙伴们点赞👍收藏✨加关注哟💕💕 

     🌟前言       

            前面我们已经学习了顺序表和单链表,顺序表可以存储动态的数据,但是一旦元素过少,而又要开辟空间,这样就造成空间的浪费,而单链表以节点为单位存储,不支持随机访问,只能从头到尾遍历访问,为了解决上面两个问题,人们发现了双链表,把一个一个元素以链子的形式存储,可以存储相互的地址,那双链表如何实现呢,今天咱们就实现一下--《双链表》。

    🌙主体

    咱们从三个方面实现双链表,动态管理,头插头删尾插尾删,增删查改。

    在程序中为了实现双链表,需要创建头文件List.h ,创建源文件Test.c,List.c。

     🌠动态管理

    💤初始化动态双链表

    既然实现双链表,初始化动态的双链表必不可少,从两个方面实现初始化动态的双链表。

    1.首先我们在List.h定义动态的双链表,省得我们再定义节点(双链表)。

    1. //定义数据类型
    2. typedef int LTDataType;
    3. //定义双链表初始化
    4. typedef struct ListNode
    5. {
    6. //上一个
    7. struct ListNode* next;
    8. //下一个
    9. struct ListNode* prev;
    10. LTDataType data;
    11. }LTNode;

    2.对双链表进行初始化

    我们要明白,这里不像单链表一样,形成节点就行,还需要初始化。

    💦这里采用malloc开辟空间

    💦采用预指令判断空间是否开辟完成(没有开辟空间返回-1)

    💦之后就是简单的初始数据

    💦记得返回值

    1. //初始化
    2. LTNode* BuyLTNode(LTDataType x)
    3. {
    4. //开辟空间
    5. LTNode* node = (LTNode*)malloc(sizeof(LTNode));
    6. //判断开辟的空间是否为空
    7. if (node == NULL)
    8. {
    9. perror("malloc fail");
    10. exit(-1);
    11. }
    12. //初始化
    13. node->data = x;
    14. node->next = NULL;
    15. node->prev = NULL;
    16. return node;
    17. }
    18. //形成双链表
    19. LTNode* LTInit()
    20. {
    21. //使头为0
    22. LTNode* phead = BuyLTNode(0);
    23. //构成循环
    24. phead->next = phead;
    25. phead->prev = phead;
    26. return phead;
    27. }

     💤释放双链表内存 

    双链表的内存释放与单链表的内存释放有一定的区别,这里我们分开两类,清理与销毁

    清理的代码如下:

    1. //清理
    2. void ListClear(LTNode* phead)
    3. {
    4. //断言
    5. assert(phead);
    6. //清理全部数据,保留头结点
    7. LTNode* cur = phead->next;
    8. //循环销毁
    9. while (cur != phead)
    10. {
    11. LTNode* next = cur->next;
    12. free(cur);
    13. cur = NULL;
    14. cur = next;
    15. }
    16. }

    销毁的代码如下:

    1. //销毁
    2. void ListDestory(LTNode** pphead)
    3. {
    4. //断言
    5. assert(*pphead);
    6. //调用清理(函数)
    7. ListClear(*pphead);
    8. //释放内存
    9. free(*pphead);
    10. *pphead = NULL;
    11. }

    🌠头插头删尾插尾删

    💤打印元素

     打印元素就太简单了,直接上代码

    1. //打印数据
    2. void LTPrint(LTNode* phead)
    3. {
    4. //断言
    5. assert(phead);
    6. printf("phead<=>");
    7. LTNode* cur = phead->next;
    8. //注意循环的结束的语句
    9. while (cur != phead)
    10. {
    11. printf("%d<=>", cur->data);
    12. cur = cur->next;
    13. }
    14. printf("\n");
    15. }

    💤尾插(重点)

    有了这个图就对指向的改变就轻轻松松

    1. //尾插(重点)
    2. void LTPushBack(LTNode* phead, LTDataType x)
    3. {
    4. //断言
    5. assert(phead);
    6. LTNode* tail = phead->prev;
    7. //给添加的元素创建节点
    8. LTNode* newnode = BuyLTNode(x);
    9. //新开辟的元素下一个节点指向尾
    10. newnode->prev = tail;
    11. //尾的上一个节点指向新的元素
    12. tail->next = newnode;
    13. //新元素的上一个节点指向头
    14. newnode->next = phead;
    15. //头的下一节点指向新的元素节点
    16. phead->prev = newnode;
    17. //本质上与尾插相似 (双向链表在pos的前面进行插入)
    18. //LTInsert(phead, x);
    19. }

    💤尾删(重点)

    双链表重在画图,希望小伙伴们能看得懂。

    1. //尾删
    2. void LTPopBack(LTNode* phead)
    3. {
    4. //断言
    5. assert(phead);
    6. //防止没有头指向没有元素
    7. assert(phead->next != phead);
    8. //找到尾
    9. LTNode* tail = phead->prev;
    10. //找到尾前面一个元素
    11. LTNode* tailPrev = tail->prev;
    12. //释放内存
    13. free(tail);
    14. //(把尾的上一个元素)的上一个指针 指向头
    15. tailPrev->next = phead;
    16. //头的下一个指针指向尾的前一个元素
    17. phead->prev = tailPrev;
    18. // 双向链表删除pos位置的结点(本质上与尾删相似)
    19. //LTErase(phead->prev);
    20. }

     💤头插(重点)

    博主在这里采用三种方法,希望大家至少学会一种

    1. //头插
    2. void LTPushFront(LTNode* phead, LTDataType x)
    3. {
    4. assert(phead);
    5. //方法一
    6. //初始化
    7. //LTNode* newnode = BuyLTNode(x);
    8. //newnode->next = phead->next;
    9. //phead->next->prev = newnode;
    10. //phead->next = newnode;
    11. //newnode->prev = phead;
    12. //方法二
    13. //初始化
    14. LTNode* newnode = BuyLTNode(x);
    15. LTNode* first = phead->next;
    16. phead->next = newnode;
    17. newnode->prev = phead;
    18. newnode->next = first;
    19. first->prev = newnode;
    20. //方法三
    21. // 双向链表删除pos位置的结点(本质上就是头插)
    22. //LTInsert(phead->next, x);
    23. }

     💤头删(重点)

    双链表会自带梢兵位(这个到后期博主会讲)

    1. //头删(重点)
    2. void LTPopFront(LTNode* phead)
    3. {
    4. //断言
    5. assert(phead);
    6. //头指向不能为空
    7. assert(phead->next != phead);
    8. //找到梢兵位的节点
    9. LTNode* first = phead->next;
    10. //找到头后面一个元素
    11. LTNode* second = first->next;
    12. //释放内存
    13. free(first);
    14. //梢兵位的节点指向头后面一个元素
    15. phead->next = second;
    16. //后面一个元素指向梢兵位的节点
    17. second->prev = phead;
    18. //双向链表删除pos位置的结点(本质上和头删一样)
    19. //LTErase(phead->next);
    20. }

      🌠增删查改

     💤统计双链表元素个数

    这个函数还是比较简单的,注意循环的停止条件。

    1. //统计双链表元素个数
    2. int LTSize(LTNode* phead)
    3. {
    4. //断言
    5. assert(phead);
    6. int size = 0;
    7. LTNode* cur = phead->next;
    8. while (cur != phead)
    9. {
    10. ++size;
    11. cur = cur->next;
    12. }
    13. return size;
    14. }

     💤双向链表在pos的前面进行插入

    这里大家就看图理解就行啦

    1. // 双向链表在pos的前面进行插入
    2. void LTInsert(LTNode* pos, LTDataType x)
    3. {
    4. //断言
    5. assert(pos);
    6. LTNode* posPrev = pos->prev;
    7. //为插入的元素开辟空间
    8. LTNode* newnode = BuyLTNode(x);
    9. posPrev->next = newnode;
    10. newnode->prev = posPrev;
    11. newnode->next = pos;
    12. pos->prev = newnode;
    13. }

     💤双向链表删除pos位置的结点

    这里大家就看图理解就行啦

    1. // 双向链表删除pos位置的结点
    2. void LTErase(LTNode* pos)
    3. {
    4. //断言
    5. assert(pos);
    6. LTNode* posPrev = pos->prev;
    7. LTNode* posNext = pos->next;
    8. //释放内存
    9. free(pos);
    10. posPrev->next = posNext;
    11. posNext->prev = posPrev;
    12. }

    🌙代码总结

    🌠主函数

    1. //包含文件
    2. #include"List.h"
    3. void TestList1()
    4. {
    5. LTNode* plist = LTInit();
    6. LTPushBack(plist, 1);
    7. LTPushBack(plist, 2);
    8. LTPushBack(plist, 3);
    9. LTPushBack(plist, 4);
    10. LTPushBack(plist, 5);
    11. LTPrint(plist);
    12. LTPushFront(plist, 10);
    13. LTPushBack(plist, 10);
    14. LTPrint(plist);
    15. }
    16. void TestList2()
    17. {
    18. LTNode* plist = LTInit();
    19. LTPushBack(plist, 1);
    20. LTPushBack(plist, 2);
    21. LTPushBack(plist, 3);
    22. LTPushBack(plist, 4);
    23. LTPushBack(plist, 5);
    24. LTPrint(plist);
    25. LTPopBack(plist);
    26. //LTPopFront(plist);
    27. LTPrint(plist);
    28. //LTPopFront(plist);
    29. //LTPopFront(plist);
    30. //LTPopFront(plist);
    31. //LTPopFront(plist);
    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. // LTPrint(plist);
    43. //
    44. // LTPushFront(plist, 10);
    45. // LTPushFront(plist, 20);
    46. // LTPushFront(plist, 30);
    47. // LTPushFront(plist, 40);
    48. // LTPrint(plist);
    49. //}
    50. //
    51. //void TestList4()
    52. //{
    53. // LTNode* plist = LTInit();
    54. // LTPushBack(plist, 1);
    55. // LTPushBack(plist, 2);
    56. // LTPushBack(plist, 3);
    57. // LTPushBack(plist, 4);
    58. // LTPushBack(plist, 5);
    59. // LTPrint(plist);
    60. //
    61. // LTPopFront(plist);
    62. // LTPrint(plist);
    63. //
    64. // LTPopBack(plist);
    65. // LTPrint(plist);
    66. //}
    67. int main()
    68. {
    69. TestList1();
    70. return 0;
    71. }

    🌠List.h头文件

    1. //包含头文件
    2. #include
    3. #include
    4. #include
    5. //定义数据类型
    6. typedef int LTDataType;
    7. //定义双链表初始化
    8. typedef struct ListNode
    9. {
    10. //上一个
    11. struct ListNode* next;
    12. //下一个
    13. struct ListNode* prev;
    14. LTDataType data;
    15. }LTNode;
    16. //初始化
    17. LTNode* BuyLTNode(LTDataType x);
    18. //形成双链表
    19. LTNode* LTInit();
    20. //打印数据
    21. void LTPrint(LTNode* phead);
    22. //尾插(重点)
    23. void LTPushBack(LTNode* phead, LTDataType x);
    24. //尾删(重点)
    25. void LTPopBack(LTNode* phead);
    26. //头插(重点)
    27. void LTPushFront(LTNode* phead, LTDataType x);
    28. //头删(重点)
    29. void LTPopFront(LTNode* phead);
    30. //统计双链表元素个数
    31. int LTSize(LTNode* phead);
    32. // 双向链表查找(在双链表中不合适)
    33. LTNode* LTFind(LTNode* phead, LTDataType x);
    34. // 双向链表在pos的前面进行插入
    35. void LTInsert(LTNode* pos, LTDataType x);
    36. // 双向链表删除pos位置的结点
    37. void LTErase(LTNode* pos);
    38. //清理
    39. void ListClear(LTNode* phead);
    40. //销毁
    41. void ListDestory(LTNode** pphead);

    🌠List.c源文件

    1. //包含文件
    2. #include"List.h"
    3. //初始化
    4. LTNode* BuyLTNode(LTDataType x)
    5. {
    6. //开辟空间
    7. LTNode* node = (LTNode*)malloc(sizeof(LTNode));
    8. //判断开辟的空间是否为空
    9. if (node == NULL)
    10. {
    11. perror("malloc fail");
    12. exit(-1);
    13. }
    14. //初始化
    15. node->data = x;
    16. node->next = NULL;
    17. node->prev = NULL;
    18. return node;
    19. }
    20. //形成双链表
    21. LTNode* LTInit()
    22. {
    23. //使头为0
    24. LTNode* phead = BuyLTNode(0);
    25. //构成循环
    26. phead->next = phead;
    27. phead->prev = phead;
    28. return phead;
    29. }
    30. //打印数据
    31. void LTPrint(LTNode* phead)
    32. {
    33. //断言
    34. assert(phead);
    35. printf("phead<=>");
    36. LTNode* cur = phead->next;
    37. //注意循环的结束的语句
    38. while (cur != phead)
    39. {
    40. printf("%d<=>", cur->data);
    41. cur = cur->next;
    42. }
    43. printf("\n");
    44. }
    45. //尾插(重点)
    46. void LTPushBack(LTNode* phead, LTDataType x)
    47. {
    48. //断言
    49. assert(phead);
    50. LTNode* tail = phead->prev;
    51. //给添加的元素创建节点
    52. LTNode* newnode = BuyLTNode(x);
    53. //新开辟的元素下一个节点指向尾
    54. newnode->prev = tail;
    55. //尾的上一个节点指向新的元素
    56. tail->next = newnode;
    57. //新元素的上一个节点指向头
    58. newnode->next = phead;
    59. //头的下一节点指向新的元素节点
    60. phead->prev = newnode;
    61. //本质上与尾插相似 (双向链表在pos的前面进行插入)
    62. //LTInsert(phead, x);
    63. }
    64. //尾删
    65. void LTPopBack(LTNode* phead)
    66. {
    67. //断言
    68. assert(phead);
    69. //防止没有头指向没有元素
    70. assert(phead->next != phead);
    71. //找到尾
    72. LTNode* tail = phead->prev;
    73. //找到尾前面一个元素
    74. LTNode* tailPrev = tail->prev;
    75. //释放内存
    76. free(tail);
    77. //(把尾的上一个元素)的上一个指针 指向头
    78. tailPrev->next = phead;
    79. //头的下一个指针指向尾的前一个元素
    80. phead->prev = tailPrev;
    81. // 双向链表删除pos位置的结点(本质上与尾删相似)
    82. //LTErase(phead->prev);
    83. }
    84. //头插
    85. void LTPushFront(LTNode* phead, LTDataType x)
    86. {
    87. assert(phead);
    88. //方法一
    89. //初始化
    90. //LTNode* newnode = BuyLTNode(x);
    91. //newnode->next = phead->next;
    92. //phead->next->prev = newnode;
    93. //phead->next = newnode;
    94. //newnode->prev = phead;
    95. //方法二
    96. //初始化
    97. LTNode* newnode = BuyLTNode(x);
    98. LTNode* first = phead->next;
    99. phead->next = newnode;
    100. newnode->prev = phead;
    101. newnode->next = first;
    102. first->prev = newnode;
    103. //方法三
    104. // 双向链表删除pos位置的结点(本质上就是头插)
    105. //LTInsert(phead->next, x);
    106. }
    107. //头删(重点)
    108. void LTPopFront(LTNode* phead)
    109. {
    110. //断言
    111. assert(phead);
    112. //头指向不能为空
    113. assert(phead->next != phead);
    114. //找到梢兵位的节点
    115. LTNode* first = phead->next;
    116. //找到头后面一个元素
    117. LTNode* second = first->next;
    118. //释放内存
    119. free(first);
    120. //梢兵位的节点指向头后面一个元素
    121. phead->next = second;
    122. //后面一个元素指向梢兵位的节点
    123. second->prev = phead;
    124. //双向链表删除pos位置的结点(本质上和头删一样)
    125. //LTErase(phead->next);
    126. }
    127. //统计双链表元素个数
    128. int LTSize(LTNode* phead)
    129. {
    130. //断言
    131. assert(phead);
    132. int size = 0;
    133. LTNode* cur = phead->next;
    134. while (cur != phead)
    135. {
    136. ++size;
    137. cur = cur->next;
    138. }
    139. return size;
    140. }
    141. // 双向链表在pos的前面进行插入
    142. void LTInsert(LTNode* pos, LTDataType x)
    143. {
    144. //断言
    145. assert(pos);
    146. LTNode* posPrev = pos->prev;
    147. //为插入的元素开辟空间
    148. LTNode* newnode = BuyLTNode(x);
    149. posPrev->next = newnode;
    150. newnode->prev = posPrev;
    151. newnode->next = pos;
    152. pos->prev = newnode;
    153. }
    154. // 双向链表删除pos位置的结点
    155. void LTErase(LTNode* pos)
    156. {
    157. //断言
    158. assert(pos);
    159. LTNode* posPrev = pos->prev;
    160. LTNode* posNext = pos->next;
    161. //释放内存
    162. free(pos);
    163. posPrev->next = posNext;
    164. posNext->prev = posPrev;
    165. }
    166. //清理
    167. void ListClear(LTNode* phead)
    168. {
    169. //断言
    170. assert(phead);
    171. //清理全部数据,保留头结点
    172. LTNode* cur = phead->next;
    173. //循环销毁
    174. while (cur != phead)
    175. {
    176. LTNode* next = cur->next;
    177. free(cur);
    178. cur = NULL;
    179. cur = next;
    180. }
    181. }
    182. //销毁
    183. void ListDestory(LTNode** pphead)
    184. {
    185. //断言
    186. assert(*pphead);
    187. //调用清理(函数)
    188. ListClear(*pphead);
    189. //释放内存
    190. free(*pphead);
    191. *pphead = NULL;
    192. }

    🌟结束语

           今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小说手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

  • 相关阅读:
    微服务框架 SpringCloud微服务架构 5 Nacos 5.3 服务多级存储模型
    微信小程序使用本地存储方法
    ARM Linux DIY(十四)摄像头捕获画面显示到屏幕
    803_div2(Rising Sand, 接受军训!
    【前沿技术RPA】 一文了解UiPath的代码审查工具Workflow Analyzer
    任意代码执行漏洞复现
    网络层学习常见问题及答案整理
    if和, && ||
    linux设备模型:bus概念及pci_bus分析
    Kafka常见问题解析
  • 原文地址:https://blog.csdn.net/AAlykk/article/details/132776974