• 【数据结构】双向链表(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)
    只需修改指针指向
    插⼊
    动态顺序表,空间不够时需要扩容
    没有容量的概念
    应⽤场景
    元素⾼效存储+频繁访问
    任意位置插⼊和删除频繁

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

  • 相关阅读:
    Java线程通信
    什么是前端框架中的数据绑定(data binding)?有哪些类型的数据绑定?
    Gateway服务网关
    机器学习基本知识(2)
    frp内网穿透搭建-宝塔版
    自动驾驶寻找「商业闭环」
    2023品牌新媒体矩阵营销洞察报告:流量内卷下,如何寻找增长新引擎?
    【【C语言康复训练-2】】
    SARscape雷达图像处理软件简介
    基于SpringBoot+Vue+uniapp的点餐平台系统(源码+lw+部署文档+讲解等)
  • 原文地址:https://blog.csdn.net/2402_82689232/article/details/139631414