分享

链表...

 醉人说梦 2023-02-04 发布于广东

今天我们继续来完成链表,但是今天我们来实现双向带头循环链表,这与我们之前写的单项不带头非循环链表相对应,链表一共可以有8种形式,学会了这两种,其他6种也都游刃有余。

(7条消息) 链表与其多种接口实现1_KLZUQ的博客-CSDN博客

 这是上一期的链表,大家可以参考一下。

还是和之前一样,我们使用工程一点的写法

我们先来完成链表的结构创建已经宏定义

  1. typedef int LTDataType;
  2. typedef struct ListNode {
  3. struct ListNode* next;
  4. struct ListNode* prev;
  5. LTDataType data;
  6. }ListNode;

 接着我们来完成链表的初始化

  1. ListNode* ListInit()//初始化
  2. {
  3. ListNode* phead = BuyListNode(0);
  4. phead->next = phead;
  5. phead->prev = phead;
  6. return phead;
  7. }

因为初始化时要创建新的节点,并且我们在之后也需要一直创建新的节点,所以我们把创建新节点封装成一个函数

  1. ListNode* BuyListNode(LTDataType x)//创建新节点
  2. {
  3. ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
  4. newnode->data = x;
  5. newnode->next = NULL;
  6. newnode->prev = NULL;
  7. return newnode;
  8. }

有了这些,我们来完成链表的尾插

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

双向带头循环链表的结构看起来很复杂,但我们实现它时却非常简单。加断言是为了防止头节点为空(比如使用链表时因为某些原因导致程序崩溃,断言可以帮助你迅速找到错误的位置),这个链表的结构非常完美,链表不为空和为空时都可以完成自己的工作,容错率非常高。

有了尾插,我们接着来实现打印,方便我们观察和调试

  1. void ListPrint(ListNode* phead)//打印
  2. {
  3. ListNode* cur = phead->next;
  4. while (cur != phead) {
  5. printf("%d ", cur->data);
  6. cur = cur->next;
  7. }
  8. printf("\n");
  9. }

我们看到,当链表为空时它仍然不会出现错误,这个结构是最优的,我在结尾会告诉大家这个链表的真正优点,接下来就不再啰嗦。

有了打印,我们来测试一下我们的代码,还是那句话,写一步测一步,不然写到后面几十个错误心态就炸了

很好,我们接着来完成头插

  1. void ListPushFront(ListNode* phead, LTDataType x)//头插
  2. {
  3. assert(phead);
  4. ListNode* first = phead->next;
  5. ListNode* newnode = BuyListNode(x);
  6. first->prev = newnode;
  7. newnode->next = first;
  8. phead->next = newnode;
  9. newnode->prev = phead;
  10. }

很多人在别的书上看到头插可能有顺序问题,但我们这个是不需要考虑顺序的,如果去掉first的话直接来完成才需要考虑顺序问题。那时候,我们需要先把newnode和它的后续节点连起来,然后再连接头节点和newnode

接着我们来完成头删

  1. void ListPopFront(ListNode* phead)//头删
  2. {
  3. assert(phead);
  4. assert(phead->next!=phead);
  5. ListNode* first = phead->next;
  6. ListNode* second = first->next;
  7. phead->next = second;
  8. second->prev = phead;
  9. free(first);
  10. first = NULL;
  11. }

我们创建first和second两个指针,这样就可以不用考虑顺序问题。第二个断言可以防止链表删除头节点。

接着是尾删

  1. void ListPopBack(ListNode* phead)//尾删
  2. {
  3. assert(phead);
  4. assert(phead->next != phead);
  5. ListNode* tail = phead->prev;
  6. ListNode* prev = tail->prev;
  7. prev->next = phead;
  8. phead->prev = prev;
  9. free(tail);
  10. tail = NULL;
  11. }

尾删同样创建两个指针,不考虑顺序

然后是查找

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

查找也是相当简单,这个查找我们也可以用来修改当前位置的值

 非常的好用

接着我们来写任意位置插入

  1. void ListInsert(ListNode* pos, LTDataType x)//任意位置前插入(pos前)
  2. {
  3. assert(pos);
  4. ListNode* prev = pos->prev;
  5. ListNode* newnode = BuyListNode(x);
  6. newnode->next = pos;
  7. pos->prev = newnode;
  8. newnode->prev = prev;
  9. prev->next = newnode;
  10. }

同样,定义一个指针可以让我们不用考虑顺序。这个配合查找使用非常方便

 然后我们来写任意位置删除

  1. void ListErase(ListNode* pos)//任意位置删除(pos位置)
  2. {
  3. assert(pos);
  4. ListNode* prev = pos->prev;
  5. ListNode* next = pos->next;
  6. prev->next = next;
  7. next->prev = prev;
  8. free(pos);
  9. }

到这里,我们的接口也基本都实现了,如果让大家在10分钟内实现这个链表和它的接口,大家可以完成吗?

这里我们就要来提高代码的复用性

我们有了任意位置的插入和删除,我们就可以修改代码

  1. void ListPopBack(ListNode* phead)//尾删
  2. {
  3. /*assert(phead);
  4. assert(phead->next != phead);
  5. ListNode* tail = phead->prev;
  6. ListNode* prev = tail->prev;
  7. prev->next = phead;
  8. phead->prev = prev;
  9. free(tail);
  10. tail = NULL;*/
  11. ListErase(phead->prev);
  12. }
  1. void ListPopFront(ListNode* phead)//头删
  2. {
  3. /*assert(phead);
  4. assert(phead->next!=phead);
  5. ListNode* first = phead->next;
  6. ListNode* second = first->next;
  7. phead->next = second;
  8. second->prev = phead;
  9. free(first);
  10. first = NULL;*/
  11. ListErase(phead->next);
  12. }

最后,我们来写一下清空链表

  1. void ListDestory(ListNode* phead)//清空
  2. {
  3. assert(phead);
  4. ListNode* cur = phead->next;
  5. while (cur != phead) {
  6. ListNode* next = cur->next;
  7. free(cur);
  8. cur = next;
  9. }
  10. free(phead);
  11. phead = NULL;
  12. }

以上就是我们完成的所有接口,大家自己试一下会发现,这个链表的结构非常完美,它的插入和删除的时间复杂度都是o(1),只有查找我们是用的是正常方法,时间复杂度为o(n)。

最后附上所有代码,希望各位有所收获

  1. #pragma once
  2. //List.h
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<assert.h>
  6. #include<stdbool.h>
  7. typedef int LTDataType;
  8. typedef struct ListNode {
  9. struct ListNode* next;
  10. struct ListNode* prev;
  11. LTDataType data;
  12. }ListNode;
  13. void ListPrint(ListNode* phead);//打印
  14. ListNode* ListInit();//初始化
  15. void ListDestory(ListNode* phead);//清空
  16. void ListPushBack(ListNode* phead, LTDataType x);//尾插
  17. void ListPushFront(ListNode* phead, LTDataType x);//头插
  18. void ListPopBack(ListNode* phead);//尾删
  19. void ListPopFront(ListNode* phead);//头删
  20. ListNode* ListFind(ListNode* phead, LTDataType x);//查找
  21. void ListInsert(ListNode* pos, LTDataType x);//任意位置前插入(pos前)
  22. void ListErase(ListNode* pos);//任意位置删除(pos位置)
  1. //List.c
  2. #include"List.h"
  3. ListNode* BuyListNode(LTDataType x)//创建新节点
  4. {
  5. ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
  6. newnode->data = x;
  7. newnode->next = NULL;
  8. newnode->prev = NULL;
  9. return newnode;
  10. }
  11. ListNode* ListInit()//初始化
  12. {
  13. ListNode* phead = BuyListNode(0);
  14. phead->next = phead;
  15. phead->prev = phead;
  16. return phead;
  17. }
  18. void ListDestory(ListNode* phead)//清空
  19. {
  20. assert(phead);
  21. ListNode* cur = phead->next;
  22. while (cur != phead) {
  23. ListNode* next = cur->next;
  24. free(cur);
  25. cur = next;
  26. }
  27. free(phead);
  28. phead = NULL;
  29. }
  30. void ListPrint(ListNode* phead)//打印
  31. {
  32. ListNode* cur = phead->next;
  33. while (cur != phead) {
  34. printf("%d ", cur->data);
  35. cur = cur->next;
  36. }
  37. printf("\n");
  38. }
  39. void ListPushBack(ListNode* phead, LTDataType x)//尾插
  40. {
  41. assert(phead);
  42. ListNode* tail = phead->prev;
  43. ListNode* newnode = BuyListNode(x);
  44. tail->next = newnode;
  45. newnode->prev = tail;
  46. phead->prev = newnode;
  47. newnode->next = phead;
  48. }
  49. void ListPushFront(ListNode* phead, LTDataType x)//头插
  50. {
  51. assert(phead);
  52. ListNode* first = phead->next;
  53. ListNode* newnode = BuyListNode(x);
  54. first->prev = newnode;
  55. newnode->next = first;
  56. phead->next = newnode;
  57. newnode->prev = phead;
  58. }
  59. void ListPopBack(ListNode* phead)//尾删
  60. {
  61. /*assert(phead);
  62. assert(phead->next != phead);
  63. ListNode* tail = phead->prev;
  64. ListNode* prev = tail->prev;
  65. prev->next = phead;
  66. phead->prev = prev;
  67. free(tail);
  68. tail = NULL;*/
  69. ListErase(phead->prev);
  70. }
  71. void ListPopFront(ListNode* phead)//头删
  72. {
  73. /*assert(phead);
  74. assert(phead->next!=phead);
  75. ListNode* first = phead->next;
  76. ListNode* second = first->next;
  77. phead->next = second;
  78. second->prev = phead;
  79. free(first);
  80. first = NULL;*/
  81. ListErase(phead->next);
  82. }
  83. ListNode* ListFind(ListNode* phead, LTDataType x)//查找
  84. {
  85. assert(phead);
  86. ListNode* cur = phead->next;
  87. while (cur != phead) {
  88. if (cur->data == x) {
  89. return cur;
  90. }
  91. cur = cur->next;
  92. }
  93. return NULL;
  94. }
  95. void ListInsert(ListNode* pos, LTDataType x)//任意位置前插入(pos前)
  96. {
  97. assert(pos);
  98. ListNode* prev = pos->prev;
  99. ListNode* newnode = BuyListNode(x);
  100. newnode->next = pos;
  101. pos->prev = newnode;
  102. newnode->prev = prev;
  103. prev->next = newnode;
  104. }
  105. void ListErase(ListNode* pos)//任意位置删除(pos位置)
  106. {
  107. assert(pos);
  108. ListNode* prev = pos->prev;
  109. ListNode* next = pos->next;
  110. prev->next = next;
  111. next->prev = prev;
  112. free(pos);
  113. }
  1. //test.c
  2. #include"List.h"
  3. void test1() {
  4. ListNode* plist = ListInit();
  5. ListPushBack(plist, 1);
  6. ListPushBack(plist, 2);
  7. ListPushBack(plist, 3);
  8. ListPushBack(plist, 4);
  9. ListPrint(plist);
  10. ListPushFront(plist, 9);
  11. ListPushFront(plist, 8);
  12. ListPushFront(plist, 7);
  13. ListPushFront(plist, 6);
  14. ListPrint(plist);
  15. ListPopFront(plist);
  16. ListPrint(plist);
  17. ListDestory(plist);
  18. }
  19. void test2() {
  20. ListNode* plist = ListInit();
  21. ListPushBack(plist, 1);
  22. ListPushBack(plist, 2);
  23. ListPushBack(plist, 3);
  24. ListPushBack(plist, 4);
  25. ListPrint(plist);
  26. ListNode* pos = ListFind(plist, 3);
  27. if (pos) {
  28. ListInsert(pos, 20);
  29. }
  30. else {
  31. printf("没找到\n");
  32. }
  33. ListPrint(plist);
  34. ListDestory(plist);
  35. }
  36. int main() {
  37. test2();
  38. return 0;
  39. }

如有错误,还请指正。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多