• 【数据结构】——双链表(增删查改)


     

    目录

    前言:

    一:双链表的定义

    ​编辑 二:双向链表的实现

    2.1:链表的构造

    2.2:创建头节点

    2.3:创建节点 

    2.4:链表的尾插 

    2.5:链表的打印

    2.6:链表的尾删

    2.7:链表的头插

    2.8:链表的头删

    2.9:链表的查找 

    2.10:在目标位置前面插入

    2.11:删除目标位置结点

    2.12:链表的销毁

     总代码:

    test.c

    List.c

     List.h


     

    前言:

    双链表的引入是因为单链表要访问某个结点的前驱结点时,只能从头开始遍历,访问后驱结点的复杂度为O(1),访问前驱结点的复杂度为O(n)。为了克服上述缺点,引入了双链表。

    双链表的引进,对于链表的操作有了极大的遍历;

    一:双链表的定义

    链表由单向的链变成了双向链。

    双向链表(double linked list)是在单链表的每个结点中再设置一个指向其前驱结点的指针域。 

     二:双向链表的实现

    2.1:链表的构造

    包含了一个数据域,两个指针域(指向前后驱节点)

    1. // 带头+双向+循环链表增删查改实现
    2. typedef int LTDataType;
    3. typedef struct ListNode
    4. {
    5. LTDataType data; //数据
    6. struct ListNode* next; //下一个指针域
    7. struct ListNode* prev; //上一个指针域
    8. }ListNode;

     

    2.2:创建头节点

    双向链表一般都是带头节点的,在链表中,带了头节点对于链表分割这一问题有了简单化;

    动态开辟出一块空间,前后指针都指向自己;

    1. //初始化链表头头节点
    2. ListNode* ListInit()
    3. {
    4. ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
    5. assert(phead);
    6. phead->data = -1;
    7. phead->next = phead;
    8. phead->prev = phead;
    9. return phead;
    10. }

    2.3:创建节点 

    这里置NULL,和单链表的置NULL,是一样的意思,对后续的操作提供便利;

    1. // 创建返回链表的结点.
    2. ListNode* ListCreate(LTDataType x)
    3. {
    4. ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
    5. assert(newnode);
    6. newnode->data = x;
    7. newnode->next = NULL;
    8. newnode->prev = NULL;
    9. return newnode;
    10. }

    2.4:链表的尾插 

    单链表的尾插还需要考虑是否存在第一个节点,这里直接插入即可;

    注意操作顺序

    1. // 双向链表尾插
    2. void ListPushBack(ListNode* phead, LTDataType x)
    3. {
    4. assert(phead);
    5. ListNode* newnode = ListCreate(x);
    6. struct ListNode* tail = phead->prev;
    7. // phead tail newnode
    8. //newnode->prev = phead->prev;
    9. //newnode->next = phead;
    10. //phead->prev->next = newnode;
    11. //phead->prev = newnode;
    12. //注意前后顺序
    13. newnode->prev = tail;
    14. tail->next = newnode;
    15. newnode->next = phead;
    16. phead->prev = newnode;
    17. }

    2.5:链表的打印

    这里的关键就是从哪里开始?如何判断结束(因为是循环)?

    我们可以从头结点的下一个开始打印,当遇到头结点即是结束;

    1. // 双向链表打印
    2. void ListPrint(ListNode* phead)
    3. {
    4. assert(phead);
    5. struct ListNode* cur = phead->next;
    6. printf("哨兵位<=>");
    7. while (cur != phead)
    8. {
    9. printf("%d<=>", cur->data);
    10. cur = cur->next;
    11. }
    12. printf("\n");
    13. }

     

    2.6:链表的尾删

    要多一个断言判断:如果只有一个头指针就不用操作了;

    保存尾结点的前驱,释放尾结点即可

    1. // 双向链表尾删
    2. void ListPopBack(ListNode* phead)
    3. {
    4. assert(phead);
    5. assert(phead->next != phead); //只有头指针不删
    6. struct ListNode* cur = phead->prev;
    7. struct ListNode* curPrev = cur->prev; //尾节点的上一个
    8. phead->prev = curPrev;
    9. curPrev->next = phead;
    10. free(cur);
    11. cur = NULL;
    12. }

     

    2.7:链表的头插

    和尾插操作相似,定义头节点的下一个结点,进行链接即可;

    注意顺序;

    1. // 双向链表头插
    2. void ListPushFront(ListNode* phead, LTDataType x)
    3. {
    4. assert(phead);
    5. ListNode* newnode = ListCreate(x);
    6. struct ListNode* cur = phead->next;
    7. newnode->next = cur;
    8. cur->prev = newnode;
    9. newnode->prev = phead;
    10. phead->next = newnode;
    11. }

     

    2.8:链表的头删

    删除操作一般都需要判断一下是否只有头节点。判断双向链表的条件是:phead->next != phead;

    1. // 双向链表头删
    2. void ListPopFront(ListNode* phead)
    3. {
    4. assert(phead);
    5. assert(phead->next != phead); //只有头指针不删
    6. ListNode* cur = phead->next;
    7. ListNode* next = cur->next;
    8. phead->next = next;
    9. next->prev = phead;
    10. free(cur);
    11. cur = NULL;
    12. }

    2.9:链表的查找 

    找到该结点后,返回的是指针,而不是数据,返回该指针位置,方便后续操作

    1. // 双向链表查找
    2. ListNode* ListFind(ListNode* phead, LTDataType x)
    3. {
    4. assert(phead);
    5. ListNode* cur = phead->next;
    6. while (cur != phead)
    7. {
    8. if (cur->data == x)
    9. return cur;
    10. cur = cur->next;
    11. }
    12. return NULL;
    13. }

    2.10:在目标位置前面插入

    1. // 双向链表在pos的前面进行插入
    2. void ListInsert(ListNode* pos, LTDataType x)
    3. {
    4. assert(pos); //检查pos位置是否有效
    5. ListNode* newnode = ListCreate(x);
    6. newnode->next = pos; //将newnode节点next prev 链接前后节点
    7. newnode->prev = pos->prev;
    8. pos->prev->next = newnode;
    9. pos->prev = newnode;
    10. }

     

    2.11:删除目标位置结点

    1. // 双向链表删除pos位置的节点
    2. void ListErase(ListNode* pos)
    3. {
    4. assert(pos);
    5. ListNode* posPrev = pos->prev;
    6. ListNode* next = pos->next;
    7. posPrev->next = next;
    8. next->prev = posPrev;
    9. free(pos);
    10. pos = NULL;
    11. }

     

    2.12:链表的销毁

    1. // 双向链表销毁
    2. void ListDestory(ListNode* phead)
    3. {
    4. assert(phead);
    5. ListNode* cur = phead->prev;
    6. while (cur != phead) //将除了头结点的都销毁
    7. {
    8. ListNode* curPrev = cur->prev;
    9. free(cur);
    10. cur = curPrev;
    11. }
    12. free(phead); //再释放头结点
    13. //phead = NULL;
    14. }

     总代码:

    test.c

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include"List.h"
    3. void test1()
    4. {
    5. ListNode* plist = NULL;
    6. plist = ListInit();
    7. ListPushBack(plist, 1);
    8. ListPushBack(plist, 2);
    9. ListPushBack(plist, 3);
    10. ListPushBack(plist, 4);
    11. ListPrint(plist);
    12. ListPopBack(plist);
    13. ListPrint(plist);
    14. ListPopBack(plist);
    15. ListPrint(plist);
    16. ListPopBack(plist);
    17. ListPrint(plist);
    18. ListPopBack(plist);
    19. ListPrint(plist);
    20. }
    21. void test2()
    22. {
    23. ListNode* plist = NULL;
    24. plist = ListInit();
    25. ListPrint(plist);
    26. //ͷ
    27. ListPushFront(plist, 6);
    28. ListPrint(plist);
    29. //ͷɾ
    30. ListPopFront(plist);
    31. ListPrint(plist);
    32. }
    33. void test3()
    34. {
    35. ListNode* plist = NULL;
    36. plist = ListInit();
    37. ListPushBack(plist, 1);
    38. ListPushBack(plist, 2);
    39. ListPushBack(plist, 3);
    40. ListPushBack(plist, 4);
    41. ListPrint(plist);
    42. //βɾ
    43. ListPopBack(plist);
    44. ListPopBack(plist);
    45. ListPopBack(plist);
    46. ListNode* pos = ListFind(plist,1);
    47. ListInsert(pos, 666);
    48. ListPrint(plist);
    49. ListErase(pos);
    50. ListPrint(plist);
    51. ListDestory(plist);
    52. }
    53. int main()
    54. {
    55. //test1();
    56. //test2();
    57. test3();
    58. return 0;
    59. }

    List.c

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include"List.h"
    3. //初始化链表头头节点
    4. ListNode* ListInit()
    5. {
    6. ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
    7. assert(phead);
    8. phead->data = -1;
    9. phead->next = phead;
    10. phead->prev = phead;
    11. return phead;
    12. }
    13. // 创建返回链表的头结点.
    14. ListNode* ListCreate(LTDataType x)
    15. {
    16. ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
    17. assert(newnode);
    18. newnode->data = x;
    19. newnode->next = NULL;
    20. newnode->prev = NULL;
    21. return newnode;
    22. }
    23. // 双向链表尾插
    24. void ListPushBack(ListNode* phead, LTDataType x)
    25. {
    26. assert(phead);
    27. ListNode* newnode = ListCreate(x);
    28. struct ListNode* tail = phead->prev;
    29. // phead tail newnode
    30. //newnode->prev = phead->prev;
    31. //newnode->next = phead;
    32. //phead->prev->next = newnode;
    33. //phead->prev = newnode;
    34. //注意前后顺序
    35. newnode->prev = tail;
    36. tail->next = newnode;
    37. newnode->next = phead;
    38. phead->prev = newnode;
    39. }
    40. // 双向链表打印
    41. void ListPrint(ListNode* phead)
    42. {
    43. assert(phead);
    44. struct ListNode* cur = phead->next;
    45. printf("哨兵位<=>");
    46. while (cur != phead)
    47. {
    48. printf("%d<=>", cur->data);
    49. cur = cur->next;
    50. }
    51. printf("\n");
    52. }
    53. // 双向链表尾删
    54. void ListPopBack(ListNode* phead)
    55. {
    56. assert(phead);
    57. assert(phead->next != phead); //只有头指针不删
    58. struct ListNode* cur = phead->prev;
    59. struct ListNode* curPrev = cur->prev; //尾节点的上一个
    60. phead->prev = curPrev;
    61. curPrev->next = phead;
    62. free(cur);
    63. cur = NULL;
    64. }
    65. // 双向链表头插
    66. void ListPushFront(ListNode* phead, LTDataType x)
    67. {
    68. assert(phead);
    69. ListNode* newnode = ListCreate(x);
    70. struct ListNode* cur = phead->next;
    71. newnode->next = cur;
    72. cur->prev = newnode;
    73. newnode->prev = phead;
    74. phead->next = newnode;
    75. }
    76. // 双向链表头删
    77. void ListPopFront(ListNode* phead)
    78. {
    79. assert(phead);
    80. assert(phead->next != phead); //只有头指针不删
    81. ListNode* cur = phead->next;
    82. ListNode* next = cur->next;
    83. phead->next = next;
    84. next->prev = phead;
    85. free(cur);
    86. cur = NULL;
    87. }
    88. // 双向链表查找
    89. ListNode* ListFind(ListNode* phead, LTDataType x)
    90. {
    91. assert(phead);
    92. ListNode* cur = phead->next;
    93. while (cur != phead)
    94. {
    95. if (cur->data == x)
    96. return cur;
    97. cur = cur->next;
    98. }
    99. return NULL;
    100. }
    101. // 双向链表在pos的前面进行插入
    102. void ListInsert(ListNode* pos, LTDataType x)
    103. {
    104. assert(pos); //检查pos位置是否有效
    105. ListNode* newnode = ListCreate(x);
    106. newnode->next = pos; //将newnode节点next prev 链接前后节点
    107. newnode->prev = pos->prev;
    108. pos->prev->next = newnode;
    109. pos->prev = newnode;
    110. }
    111. // 双向链表删除pos位置的节点
    112. void ListErase(ListNode* pos)
    113. {
    114. assert(pos);
    115. ListNode* posPrev = pos->prev;
    116. ListNode* next = pos->next;
    117. posPrev->next = next;
    118. next->prev = posPrev;
    119. free(pos);
    120. pos = NULL;
    121. }
    122. // 双向链表销毁
    123. void ListDestory(ListNode* phead)
    124. {
    125. assert(phead);
    126. ListNode* cur = phead->prev;
    127. while (cur != phead) //将除了头节点的都销毁
    128. {
    129. ListNode* curPrev = cur->prev;
    130. free(cur);
    131. cur = curPrev;
    132. }
    133. free(phead);
    134. //phead = NULL;
    135. }

     List.h

    1. #pragma once
    2. #include<stdio.h>
    3. #include<assert.h>
    4. #include<stdlib.h>
    5. // 带头+双向+循环链表增删查改实现
    6. typedef int LTDataType;
    7. typedef struct ListNode
    8. {
    9. LTDataType data; //数据
    10. struct ListNode* next; //下一个指针域
    11. struct ListNode* prev; //上一个指针域
    12. }ListNode;
    13. // 创建返回链表的头结点.
    14. ListNode* ListCreate(LTDataType x);
    15. //初始化链表头头节点
    16. ListNode* ListInit();
    17. // 双向链表尾插
    18. void ListPushBack(ListNode* pHead, LTDataType x);
    19. // 双向链表打印
    20. void ListPrint(ListNode* pHead);
    21. // 双向链表尾删
    22. void ListPopBack(ListNode* pHead);
    23. // 双向链表头插
    24. void ListPushFront(ListNode* pHead, LTDataType x);
    25. // 双向链表头删
    26. void ListPopFront(ListNode* pHead);
    27. // 双向链表查找
    28. ListNode* ListFind(ListNode* pHead, LTDataType x);
    29. // 双向链表在pos的前面进行插入
    30. void ListInsert(ListNode* pos, LTDataType x);
    31. // 双向链表删除pos位置的节点
    32. void ListErase(ListNode* pos);
    33. // 双向链表销毁
    34. void ListDestory(ListNode* pHead);

     以上就是我对【数据结构|双向链表|增删改查】的介绍,不足之处,还望指点。

     

  • 相关阅读:
    虚拟机(三)VMware Workstation 桥接模式下无法上网
    SpringSecurity6从入门到上天系列第七篇:讲明白SpringBoot的自动装配完善上篇文章中的结论
    C++ STL有用?如何调试?
    [笔记]CentOS7 vsftpd安装及配置使用
    RPA是什么?推荐让电商运营10倍提效的自动化工具
    static关键字总结-C/C++
    04.JavaScript(深浅拷贝、异常处理、改变this执行)
    听我一句劝好吗?放下那些老掉牙的性能优化笔记吧!又不是没有新的,跟不上时代的学了也没法直接用呀!
    我,PolarDB云原生数据库,5年来实现这些重磅技术创新
    Node.js 入门(1):Node 简介和安装
  • 原文地址:https://blog.csdn.net/m0_67367079/article/details/134507170