• 【数据结构】双向链表


    目录

    1.双向链表的结构

    2.双向链表的实现

    1.双向链表的初始化

     2.尾部插入

    3.打印链表

    4.头部插入

    5.尾删

     6.头删

    7.查找

    8.在指定位置之后插入节点

    9.删除指定位置的节点 

    10.销毁链表

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


    1.双向链表的结构

    我们观察双向链表的结构可以发现一个节点不仅可以指向后一个节点,同时也可以指向前一个节点。

    这里的head跟在之前的单链表中我们说的“头节点”是两个概念,实际在之前的在单链表阶段称呼不严谨,但是为了让我们更好的理解就直接称为单链表的头节点。

    带头链表⾥的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这里放哨的。

     “哨兵位”存在的意义: 遍历循环链表避免死循环。

    2.双向链表的实现

    双向链表的结构体:

    1. typedef int LTDataType;
    2. typedef struct ListNode
    3. {
    4. struct ListNode* next; //指针保存下⼀个节点的地址
    5. struct ListNode* prev; //指针保存前⼀个节点的地址
    6. LTDataType data;
    7. }LTNode;

    结构体函数的实现:

    1.双向链表的初始化

    初始化的过程中需要我们创建一个哨兵位,这就又需要我们申请一个节点,由于以后的函数也要用到申请节点的代码所以我们将申请节点独立封装成一个函数:

    1. LT* LTBuyNode(LTDataType x)
    2. {
    3. LT* node = (LT*)malloc(sizeof(LT));
    4. if (node == NULL)
    5. {
    6. perror("malloc");
    7. exit(1);
    8. }
    9. node->data = x;
    10. node->next = node->prev = node;
    11. }

    然后直接使用这个函数完成链表的初始化就可以了。

    1. void LTInit(LT** pphead)
    2. {
    3. *pphead = LTBuyNode(-1);
    4. }

     2.尾部插入

    尾部插入首先需要我们申请一个新节点,再将其插入到双向链表的尾部,这里为了不让插入后影响head节点和d3节点的指向,所以先我们就先将newnode节点连接d3节点和head节点上面。

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

    注意:这段代码最后两行代码不可以交换位置,不然会造成phead->prev的数据提前丢失的问题。

    3.打印链表

    为了证明我们写的代码的正确性,我们将其打印出来。

    1. void LTPrint(LT* phead)
    2. {
    3. LT* ptail = phead->next;
    4. while (ptail != phead)
    5. {
    6. printf("%d->", ptail->data);
    7. ptail = ptail->next;
    8. }
    9. printf("\n");
    10. }

    4.头部插入

     

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

    5.尾删

    尾删中的断言需要排除链表必须有效和链表必须不为空的情况(只有一个哨兵位)。

    1. void LTPopBack(LT* phead)
    2. {
    3. assert(phead&&phead!=phead->next);
    4. LT* del = phead->prev;
    5. del->prev->next = phead;
    6. phead->prev = del->prev;
    7. free(del);
    8. del = NULL;
    9. }

     6.头删

    1. void LTPopFront(LT* phead)
    2. {
    3. assert(phead && phead != phead->next);
    4. //链表必须有效和链表必须不为空的情况(只有一个哨兵位)
    5. LT* del = phead->next;
    6. del->next->prev = phead;
    7. phead->next = del->next;
    8. free(del);
    9. del = NULL;
    10. }

    7.查找

    1. LT* LTFind(LT* phead, LTDataType x)
    2. {
    3. assert(phead && phead != phead->next);
    4. LT* ptail = phead->next;
    5. while (ptail != phead)
    6. {
    7. if (ptail->data == x)
    8. {
    9. return ptail;
    10. }
    11. ptail = ptail->next;
    12. }
    13. return NULL;
    14. }

    8.在指定位置之后插入节点

    1. void LTInsert(LT* pos, LTDataType x)
    2. {
    3. assert(pos);
    4. LT* del = pos;
    5. LT* newnode = LTBuyNode(x);
    6. newnode->next = del->next;
    7. newnode->prev = del;
    8. del->next->prev = newnode;
    9. del->next = newnode;
    10. }

    9.删除指定位置的节点 

    1. void LTErase(LT* pos)
    2. {
    3. assert(pos);
    4. LT* del = pos;
    5. del->prev->next = del->next;
    6. del->next->prev = del->prev;
    7. free(del);
    8. del = NULL;
    9. }

    10.销毁链表

    在销毁链表的时候我们可以直接调用头删的函数

    1. void LTDesTroy(LT* phead)
    2. {
    3. assert(phead);
    4. LT* ptail = phead->next;
    5. while (ptail != phead)
    6. {
    7. LTPopFront(phead);
    8. ptail = phead->next;
    9. }
    10. free(ptail);
    11. ptail = NULL;
    12. }

    整体展示:

    test.c

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include"List.h"
    3. void test01()
    4. {
    5. LT* plist = NULL;
    6. LTInit(plist);
    7. //输入操作的指令
    8. LTDesTroy(plist);
    9. }
    10. int main()
    11. {
    12. test01();
    13. return 0;
    14. }

     List.c

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include"List.h"
    3. //申请节点
    4. LT* LTBuyNode(LTDataType x)
    5. {
    6. LT* node = (LT*)malloc(sizeof(LT));
    7. if (node == NULL)
    8. {
    9. perror("malloc");
    10. exit(1);
    11. }
    12. node->data = x;
    13. node->next = node->prev = node;
    14. }
    15. void LTInit(LT** pphead)
    16. {
    17. *pphead = LTBuyNode(-1);
    18. }
    19. void LTPushBack(LT* phead, LTDataType x)
    20. {
    21. assert(phead);
    22. LT* newnode = LTBuyNode(x);
    23. newnode->next = phead;
    24. newnode->prev = phead->prev;
    25. phead->prev->next = newnode;
    26. phead->prev = newnode;
    27. }
    28. void LTPrint(LT* phead)
    29. {
    30. LT* ptail = phead->next;
    31. while (ptail != phead)
    32. {
    33. printf("%d->", ptail->data);
    34. ptail = ptail->next;
    35. }
    36. printf("\n");
    37. }
    38. void LTPushFront(LT* phead, LTDataType x)
    39. {
    40. assert(phead);
    41. LT* newnode = LTBuyNode(x);
    42. newnode->next = phead->next;
    43. newnode->prev = phead;
    44. phead->next->prev = newnode;
    45. phead->next = newnode;
    46. }
    47. void LTPopBack(LT* phead)
    48. {
    49. assert(phead&&phead!=phead->next);
    50. LT* del = phead->prev;
    51. del->prev->next = phead;
    52. phead->prev = del->prev;
    53. free(del);
    54. del = NULL;
    55. }
    56. void LTPopFront(LT* phead)
    57. {
    58. assert(phead && phead != phead->next);
    59. //链表必须有效和链表必须不为空的情况(只有一个哨兵位)
    60. LT* del = phead->next;
    61. del->next->prev = phead;
    62. phead->next = del->next;
    63. free(del);
    64. del = NULL;
    65. }
    66. LT* LTFind(LT* phead, LTDataType x)
    67. {
    68. assert(phead && phead != phead->next);
    69. LT* ptail = phead->next;
    70. while (ptail != phead)
    71. {
    72. if (ptail->data == x)
    73. {
    74. return ptail;
    75. }
    76. ptail = ptail->next;
    77. }
    78. return NULL;
    79. }
    80. void LTInsert(LT* pos, LTDataType x)
    81. {
    82. assert(pos);
    83. LT* del = pos;
    84. LT* newnode = LTBuyNode(x);
    85. newnode->next = del->next;
    86. newnode->prev = del;
    87. del->next->prev = newnode;
    88. del->next = newnode;
    89. }
    90. void LTErase(LT* pos)
    91. {
    92. assert(pos);
    93. LT* del = pos;
    94. del->prev->next = del->next;
    95. del->next->prev = del->prev;
    96. free(del);
    97. del = NULL;
    98. }
    99. void LTDesTroy(LT* phead)
    100. {
    101. assert(phead);
    102. LT* ptail = phead->next;
    103. while (ptail != phead)
    104. {
    105. LTPopFront(phead);
    106. ptail = phead->next;
    107. }
    108. free(ptail);
    109. ptail = NULL;
    110. }

    List.h

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. typedef int LTDataType;
    6. typedef struct ListNode
    7. {
    8. struct ListNode* next; //指针保存下⼀个节点的地址
    9. struct ListNode* prev; //指针保存前⼀个节点的地址
    10. LTDataType data;
    11. }LT;
    12. //初始化
    13. void LTInit(LT** pphead);
    14. //尾插
    15. void LTPushBack(LT* phead, LTDataType x);
    16. //打印链表
    17. void LTPrint(LT* phead);
    18. //头插
    19. void LTPushFront(LT* phead, LTDataType x);
    20. //尾删
    21. void LTPopBack(LT* phead);
    22. //头删
    23. void LTPopFront(LT* phead);
    24. //查找
    25. LT* LTFind(LT* phead, LTDataType x);
    26. //在指定位置之后插入节点
    27. void LTInsert(LT* pos, LTDataType x);
    28. //删除指定位置的节点
    29. void LTErase(LT* pos);
    30. //销毁链表
    31. void LTDesTroy(LT* phead);

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

  • 相关阅读:
    神经元的细胞体内有什么,神经元的细胞体在哪里
    Game101作业5以及光线追踪笔记
    网站域名被墙是什么原因?有什么解决方法?
    求区间内共有多少种数字(莫队、树状数组、线段树、主席树)
    从零开始搭建个人网站①
    【问题思考】中断隐指令是硬件还是CPU执行的?(分析执行过程)
    关于编程本质那些事
    解密Elasticsearch:深入探究这款搜索和分析引擎
    Centos7上使用yum安装mysql8.x
    【protobuf 】protobuf 升级后带来的一些坑
  • 原文地址:https://blog.csdn.net/HEBRE/article/details/137844003