当前位置:   article > 正文

【数据结构】双向链表(C语言)

【数据结构】双向链表(C语言)

哈喽铁子们,这里是博主鳄鱼皮坡。这篇文章将分享交流双向链表的相关知识,下面正式开始。

1. 双向链表的结构

注意:这里的“带头”跟前面我们说的“头节点”是两个概念,实际前面的在单链表阶段称呼不严
谨,但是为了老铁们更好的理解就直接称为单链表的头节点。
带头链表里的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这⾥“放哨
的”。而“哨兵位”存在的意义: 遍历循环链表避免死循环。

2. 双向链表的实现

以尾插为例:

第一步:assert(phead); 防止为空。

第二步:创建新节点,和单链表一样用LTBuyNode()函数即可。

第三步:先将新节点指向原链表,由双向链表的特性,我们就不需要像单链表一样遍历去找。newnode->prev即为上图的d3。

       (1) newnode->prev = phead->prev;先将新节点的头部指向原链表的最后一个节点,即d3。

       (2) newnode->next = phead;而后将新节点的尾部指向原链表的哨兵位。

第四步:将原链表相应的位置指向新节点

       (1)phead->prev->next = newnode;原链表的最后节点尾部指向新节点

       (2)phead->prev = newnode;原链表的哨兵位头部指向新节点

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

只要理清楚双向链表节点的指向关系,之后和单链表结构相似。

双链表的代码如下: 

  1. //List.c
  2. #include"List.h"
  3. void LTPrint(LTNode* phead)
  4. {
  5. LTNode* pcur = phead->next;
  6. while (pcur != phead)
  7. {
  8. printf("%d->", pcur->data);
  9. pcur = pcur->next;
  10. }
  11. printf("\n");
  12. }
  13. //申请节点
  14. LTNode* LTBuyNode(LTDataType x)
  15. {
  16. LTNode* node = (LTNode*)malloc(sizeof(LTNode));
  17. if (node == NULL)
  18. {
  19. perror("malloc fail!");
  20. exit(1);
  21. }
  22. node->data = x;
  23. node->next = node->prev = node;
  24. return node;
  25. }
  26. //初始化
  27. //void LTInit(LTNode** pphead)
  28. //{
  29. // //给双向链表创建一个哨兵位
  30. // *pphead = LTBuyNode(-1);
  31. //}
  32. LTNode* LTInit()
  33. {
  34. LTNode* phead = LTBuyNode(-1);
  35. return phead;
  36. }
  37. //尾插
  38. void LTPushBack(LTNode* phead, LTDataType x)
  39. {
  40. assert(phead);
  41. LTNode* newnode = LTBuyNode(x);
  42. //phead phead->prev newnode
  43. newnode->prev = phead->prev;
  44. newnode->next = phead;
  45. phead->prev->next = newnode;
  46. phead->prev = newnode;
  47. }
  48. //头插
  49. void LTPushFront(LTNode* phead, LTDataType x)
  50. {
  51. assert(phead);
  52. LTNode* newnode = LTBuyNode(x);
  53. //phead newnode phead->next
  54. newnode->next = phead->next;
  55. newnode->prev = phead;
  56. phead->next->prev = newnode;
  57. phead->next = newnode;
  58. }
  59. //尾删
  60. void LTPopBack(LTNode* phead)
  61. {
  62. //链表必须有效且链表不能为空(只有一个哨兵位)
  63. assert(phead && phead->next != phead);
  64. LTNode* del = phead->prev;
  65. //phead del->prev del
  66. del->prev->next = phead;
  67. phead->prev = del->prev;
  68. //删除del节点
  69. free(del);
  70. del = NULL;
  71. }
  72. //头删
  73. void LTPopFront(LTNode* phead)
  74. {
  75. assert(phead && phead->next != phead);
  76. LTNode* del = phead->next;
  77. //phead del del->next
  78. phead->next = del->next;
  79. del->next->prev = phead;
  80. //删除del节点
  81. free(del);
  82. del = NULL;
  83. }
  84. LTNode* LTFind(LTNode* phead, LTDataType x)
  85. {
  86. LTNode* pcur = phead->next;
  87. while (pcur != phead)
  88. {
  89. if (pcur->data == x)
  90. {
  91. return pcur;
  92. }
  93. pcur = pcur->next;
  94. }
  95. //没有找到
  96. return NULL;
  97. }
  98. //在pos位置之后插入数据
  99. void LTInsert(LTNode* pos, LTDataType x)
  100. {
  101. assert(pos);
  102. LTNode* newnode = LTBuyNode(x);
  103. //pos newnode pos->next
  104. newnode->next = pos->next;
  105. newnode->prev = pos;
  106. pos->next->prev = newnode;
  107. pos->next = newnode;
  108. }
  109. //删除pos节点
  110. void LTErase(LTNode* pos)
  111. {
  112. //pos理论上来说不能为phead,但是没有参数phead,无法增加校验
  113. assert(pos);
  114. //pos->prev pos pos->next
  115. pos->next->prev = pos->prev;
  116. pos->prev->next = pos->next;
  117. free(pos);
  118. pos = NULL;
  119. }
  120. void LTDesTroy(LTNode* phead)
  121. {
  122. assert(phead);
  123. LTNode* pcur = phead->next;
  124. while (pcur != phead)
  125. {
  126. LTNode* next = pcur->next;
  127. free(pcur);
  128. pcur = next;
  129. }
  130. //此时pcur指向phead,而phead还没有被销毁
  131. free(phead);
  132. phead = NULL;
  133. }
  1. //List.h
  2. #pragma once
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<assert.h>
  6. //定义节点的结构
  7. //数据 + 指向下一个节点的指针
  8. typedef int SLTDataType;
  9. typedef struct SListNode {
  10. SLTDataType data;
  11. struct SListNode* next;
  12. }SLTNode;
  13. void SLTPrint(SLTNode* phead);
  14. //尾插
  15. void SLTPushBack(SLTNode** pphead, SLTDataType x);
  16. //头插
  17. void SLTPushFront(SLTNode** pphead, SLTDataType x);
  18. //尾删
  19. void SLTPopBack(SLTNode** pphead);
  20. //头删
  21. void SLTPopFront(SLTNode** pphead);
  22. //查找
  23. SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
  24. //在指定位置之前插入数据
  25. void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
  26. //在指定位置之后插入数据
  27. void SLTInsertAfter(SLTNode* pos, SLTDataType x);
  28. //删除pos节点
  29. void SLTErase(SLTNode** pphead, SLTNode* pos);
  30. //删除pos之后的节点
  31. void SLTEraseAfter(SLTNode* pos);
  32. //销毁链表
  33. void SListDesTroy(SLTNode** pphead);

 3. 顺序表和双向链表的优缺点分析

不同点
顺序表
链表(单链表)
存储空间上
物理上⼀定连续
逻辑上连续,但物理上不⼀定连续
随机访问
⽀持O(1)
不⽀持:O(N)
任意位置插⼊或者删除元素
可能需要搬移元素,效率低O(N)
只需修改指针指向
插⼊
动态顺序表,空间不够时需要扩容
没有容量的概念
应⽤场景
元素⾼效存储+频繁访问
任意位置插⼊和删除频繁

在接下来我们将会学习利用实现贪吃蛇小游戏等有意思的东西,如果本篇有不理解的地方,欢迎私信我或在评论区指出,期待与你们共同进步。创作不易,望各位大佬一键三连!

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

闽ICP备14008679号