当前位置:   article > 正文

《C语言数据结构》———链表进阶之双向链表_c语言 双向队列 读取大量的数据后,链表就断开了

c语言 双向队列 读取大量的数据后,链表就断开了

在实际生活中,我们用到的最多的两种链表结构就是单链表和双向带头链表,上一篇已经介绍了单链表的实现以及一些应用,接下来我为大家详细介绍一下双向链表,以及一些链表oj题。

文章目录

  • 一、双向链表的概念
  • 二、双向链表的实现
  • 三、链表与顺序表的差别
  • 四、链表oj
  • 总结


提示:以下是本篇文章正文内容,下面案例可供参考

一、双向链表的概念

1、概念:概念:双向链表是每个结点除后继指针外还有⼀个前驱指针。双向链表也有带头结点结构和不带头结点结构两种,带头结点的双向链表更为常用;另外,双向链表也可以有循环和非循环两种结构,循环结构的双向链表更为常用。

二、双向链表的实现

头文件List.h

  1. #pragma once
  2. #include<stdio.h>
  3. #include<assert.h>
  4. #include<stdlib.h>
  5. typedef int LTDateType;
  6. typedef struct ListNode
  7. {
  8. LTDateType date;
  9. struct ListNode* next;
  10. struct ListNode* prev;
  11. }LTNode;
  12. //void ListInit(LTNode** pphead);
  13. void ListPrint(LTNode* phead);
  14. void ListPopBack(LTNode* phead);
  15. LTNode* ListInit();//初始化
  16. LTNode* BuyLTNode(LTDateType x);
  17. void ListPushBack(LTNode* phead, LTDateType x);
  18. void ListPushFront(LTNode* phead, LTDateType x);
  19. void ListPopFront(LTNode* phead);
  20. LTNode* ListFind(LTNode* phead, LTDateType x);
  21. //在pos前插入
  22. void ListInsert(LTNode* pos, LTDateType x);
  23. //删除pos位置的节点
  24. void ListErease(LTNode* pos);
  25. void ListDestory(LTNode* phead);

源文件List.C

  1. #include"List.h"
  2. LTNode* BuyLTNode(LTDateType x)
  3. {
  4. LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  5. if (newnode == NULL)
  6. {
  7. printf("malloc fail\n");
  8. exit(-1);
  9. }
  10. newnode->date = x;
  11. newnode->next = NULL;
  12. newnode->prev = NULL;
  13. return newnode;
  14. }
  15. //void ListInit(LTNode** pphead)
  16. //{
  17. // assert(pphead);
  18. // *pphead = BuyLTNode(0);
  19. // (*pphead)->next = *pphead;
  20. // (*pphead)->prev = *pphead;
  21. //}
  22. LTNode* ListInit()
  23. {
  24. LTNode* phead = BuyLTNode(0);
  25. phead->next = phead;
  26. phead->prev = phead;
  27. return phead;
  28. }
  29. void ListPrint(LTNode* phead)
  30. {
  31. assert(phead);
  32. LTNode* cur = phead->next;
  33. while (cur != phead)
  34. {
  35. printf("%d ", cur->date);
  36. cur = cur->next;
  37. }
  38. printf("\n");
  39. }
  40. void ListPushBack(LTNode* phead, LTDateType x)
  41. {
  42. assert(phead);
  43. LTNode* tail = phead->prev;
  44. LTNode* newnode = BuyLTNode(x);
  45. tail->next = newnode;
  46. newnode->prev = tail;
  47. newnode->next = phead;
  48. phead->prev = newnode;
  49. }
  50. void ListPushFront(LTNode* phead, LTDateType x)
  51. {
  52. assert(phead);
  53. ListInsert(phead->next, x);
  54. }
  55. void ListPopBack(LTNode* phead)//尾删
  56. {
  57. assert(phead);
  58. assert(phead->next != phead);//说明传进来的不只有phead,不然phead被free掉了。
  59. //LTNode* tail = phead->prev;
  60. //LTNode* tailPrev = tail->prev;
  61. //free(tail);
  62. //tail = NULL;
  63. //tailPrev->next = phead;
  64. //phead->prev = tailPrev;
  65. ListErease(phead->prev);
  66. }
  67. void ListPopFront(LTNode* phead)//头删
  68. {
  69. assert(phead);
  70. assert(phead->next != phead);
  71. ListErease(phead->next);
  72. }
  73. LTNode* ListFind(LTNode* phead, LTDateType x)
  74. {
  75. assert(phead);
  76. LTNode* cur = phead->next;
  77. while (cur != phead)
  78. {
  79. if (cur->date == x)
  80. {
  81. return cur;
  82. }
  83. cur = cur->next;
  84. }
  85. return NULL;
  86. }
  87. //void ListInsert(LTNode* pos, LTDateType x)
  88. //{
  89. // assert(pos);
  90. // LTNode* newnode = BuyLTNode(x);
  91. // pos->prev->next = newnode;
  92. // newnode->prev = pos->prev;
  93. //
  94. // pos->prev = newnode;
  95. // newnode->next = pos;
  96. //
  97. //}
  98. //更好的写法
  99. void ListInsert(LTNode* pos, LTDateType x)
  100. {
  101. assert(pos);
  102. //无关写的顺序
  103. LTNode* newnode = BuyLTNode(x);
  104. LTNode* posPrev = pos->prev;
  105. newnode->next = pos;
  106. pos->prev = newnode;
  107. posPrev->next = newnode;
  108. newnode->prev = posPrev;
  109. }
  110. // 驼峰法
  111. //函数和类型所以单词首字母大写
  112. //变量:第一个单词小写后续单词首字母大写
  113. void ListErease(LTNode* pos)
  114. {
  115. assert(pos);
  116. LTNode* prev = pos->prev;
  117. LTNode* next = pos->next;
  118. free(pos);
  119. //此时要把pos置成空,不然pos就是野指针了
  120. pos = NULL;
  121. prev->next = next;//LT的新的prev指向pos->next;
  122. next->prev = prev;//LT的新的next的prev指向prev;
  123. }
  124. void ListDestory(LTNode* phead)
  125. {
  126. assert(phead);
  127. LTNode* cur = phead->next;//从头结点的下一个位置开始。
  128. while (cur != phead)
  129. {
  130. LTNode* next = cur->next;//先记录,再free
  131. //ListErease(cur);//副用Erease函数实现destory
  132. free(cur);//
  133. cur = next;
  134. }
  135. free(phead);
  136. //phead = NULL;形参的改变不影响实参
  137. }

三、链表与顺序表的差别

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

 四、链表oj

1、链表中倒数第k个结点_牛客题霸_牛客网(链接)

思路:快慢指针法,fast先走k步, slow和fast同时走, fast走到尾时,slow就是倒数第k个

代码示例:

  1. struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
  2. struct ListNode* slow, *fast;
  3. slow = fast = pListHead;
  4. while(k --)//走k步
  5. {
  6. //判断K是否大于链表的长度。
  7. if(fast == NULL) return NULL;
  8. fast = fast->next;
  9. }
  10. while(fast)//当fast 指针走到尾时
  11. {
  12. slow = slow->next;
  13. fast = fast->next;
  14. }
  15. return slow;
  16. }
  17. 第二种写法:
  18. struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
  19. {
  20. struct ListNode* p1 = NULL, *p2 = NULL;
  21. p1 = pListHead;
  22. p2 = pListHead;
  23. int a = k;
  24. int c = 0;//记录节点个数
  25. //p1指针先跑,并且记录节点数,当p1指针跑了k-1个节点后,p2指针开始跑,
  26. //当p1指针跑到最后时,p2所指指针就是倒数第k个节点
  27. while(p1)//当p1 不为空时
  28. {
  29. p1 = p1->next;
  30. c ++;//节点个数增加
  31. if(k < 1) p2 = p2->next;
  32. k --;
  33. }
  34. if(c < a) return NULL;
  35. return p2;
  36. }

  2、21. 合并两个有序链表(链接)
思路:归并的思想,将小的值尾插到新链表。时间复杂度为n^2;如果再定义一个尾指针, 每次记录尾。

  1. struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
  2. {
  3. if(list1 == NULL)//list1和list2分别是两个链表的头指针
  4. return list2;
  5. if(list2 == NULL)
  6. return list1;
  7. struct ListNode* head = NULL, *tail = NULL;//head是新链表的头指针,tail是新链表的尾指针
  8. while(list1 && list2)
  9. {
  10. if(list1->val < list2->val)
  11. {
  12. if(tail == NULL)//这一步不太理解
  13. {
  14. head = tail = list1;
  15. }
  16. else
  17. {
  18. tail->next = list1;//先传指针指向
  19. tail = list1;//再把list 的地址传给tail,相当于tail往前挪了一步。
  20. }
  21. list1 = list1->next;
  22. }
  23. else
  24. {
  25. if(tail == NULL)
  26. {
  27. head = tail = list2;
  28. }
  29. else
  30. {
  31. tail->next = list2;
  32. tail = list2;//传地址
  33. }
  34. list2 = list2->next;
  35. }
  36. }
  37. if(list1)
  38. {
  39. tail->next = list1;//如果链表1不为空,而此时链表二为空,则直接把链表二的值连接过去就好了。
  40. }
  41. if(list2)
  42. {
  43. tail->next = list2;
  44. }
  45. return head;
  46. }
  47. 方法二:设置一个哨兵位头结点
  48. head存随机值, head->next存第一个链表的值。起到简化代码的作用。
  49. 一般的链表没有设置哨兵位头结点。
  50. struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
  51. {
  52. struct ListNode* head = NULL, *tail = NULL;
  53. head = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
  54. head->next = NULL;
  55. while(list1 && list2)
  56. {
  57. if(list1->val < list2->val)
  58. {
  59. tail->next = list1;//先传指针指向
  60. tail = list1;//再把list 的地址传给tail,相当于tail往前挪了一步。
  61. list1 = list1->next;
  62. }
  63. else
  64. {
  65. tail->next = list2;
  66. tail = list2;//传地址
  67. list2 = list2->next;
  68. }
  69. }
  70. if(list1)
  71. {
  72. tail->next = list1;//如果链表1不为空,而此时链表二为空,则直接把链表二的值连接过去就好了。
  73. }
  74. if(list2)
  75. {
  76. tail->next = list2;
  77. }
  78. //解决内存泄漏问题;
  79. struct ListNode* list = head->next;
  80. free(head);
  81. return list;
  82. }

3、链表分割_牛客题霸_牛客网(链接)

思路:新设置两个链表,小于x的插到第一个链表,大于x的插到第二个链表。

定义四个指针:LessHead, LessTail,GreatHead, GreatTail.

原链表的值分别尾插到这两个链表, 然后分别更新LessTail,和GreatTail;

最后LessTail的指针再指向GreatHead。完成两个链表的连接。

想极端场景:

1、所有值比x小

2、所有值比x大

3、比x大和小都有,最后一个值是比x小

4、比x大和小都有,最后一个值是比x大

 


  1. 构成环链表,造成死循环。
  2. //设置哨兵位
  3. class Partition {
  4. public:
  5. ListNode* partition(ListNode* pHead, int x) {
  6. struct ListNode* lessHead, *lessTail, *greatHead, *greatTail;
  7. lessHead = lessTail = (struct ListNode*)malloc(sizeof(struct ListNode));
  8. greatHead = greatTail = (struct ListNode*)malloc(sizeof(struct ListNode));
  9. lessTail->next = greatTail->next = NULL;
  10. struct ListNode* cur = pHead;
  11. while (cur) {
  12. if (cur->val < x) {
  13. lessTail->next = cur;
  14. lessTail = lessTail->next;
  15. } else {
  16. greatTail->next = cur;
  17. greatTail = greatTail->next;
  18. }
  19. cur = cur->next;
  20. }
  21. //完成链接工作
  22. lessTail->next = greatHead->next;
  23. greatTail->next = NULL;//切断greetTail的最后与greetHead的链接
  24. struct ListNode* list = lessHead->next;
  25. free(lessHead);
  26. free(greatHead);
  27. return list;
  28. }
  29. };

4、链表的回文结构_牛客题霸_牛客网 (链接)

 思路:找出链表的中间节点, 然后逆置,判断前后链表是否一致,若一致,则说明该链表是回文结构。

为什么奇数个时也能过,

例如:1 2 3 2 1

逆置完变为了 1 2  1 2  3 ;

当A走到2的位置时2->next = 3, rHead走到的 2->next = 3, 相等。

 

  1. class PalindromeList {
  2. public:
  3. struct ListNode* middleNode(struct ListNode* head)
  4. {
  5. struct ListNode* slow, * fast;
  6. slow = fast = head;
  7. while(fast && fast->next) //想的是结束的条件,写的是继续的条件
  8. {
  9. slow = slow->next;
  10. fast = fast->next->next;
  11. }
  12. return slow;
  13. }
  14. struct ListNode* reverseList(struct ListNode* head)
  15. {
  16. struct ListNode* NewHead = NULL;
  17. struct ListNode* cur = head;
  18. while(cur)
  19. {
  20. struct ListNode* next = cur->next;
  21. //头插
  22. cur->next = NewHead;//更多代表链表的指向方向。
  23. NewHead = cur;//接着把地址传过来(更新)
  24. cur = next;//(更新)
  25. }
  26. return NewHead;
  27. }
  28. bool chkPalindrome(ListNode* A) {
  29. struct ListNode* mid = middleNode(A);
  30. struct ListNode*rHead = reverseList(mid);//应该逆置mid,中间结点。
  31. while(A && rHead)
  32. {
  33. if(A->val == rHead->val)
  34. {
  35. A = A->next;
  36. rHead = rHead->next;
  37. }
  38. else
  39. {
  40. return false;
  41. }
  42. }
  43. return true;
  44. }
  45. };

 5、力扣相交链表(链接)

思路1:A链表每个节点B跟链表依次比较,如果有相等,就是相交,第一个相等就是交点。

时间复杂度:o(N*M);

思路二:如果两个链表相交,则这两个链表的最后一个元素的地址相同。

包含思路二三:

代码示例:

  1. struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
  2. {
  3. struct ListNode* tailA, *tailB;//因为之后还要用到headA,和headB,所以要用tail来进行迭代。
  4. tailA = headA, tailB = headB;
  5. int lenA = 1, lenB = 1;
  6. while(tailA)//求出A的尾
  7. {
  8. tailA = tailA->next;
  9. ++lenA;//求出A的长度
  10. }
  11. while(tailB)//求出链表B的尾
  12. {
  13. tailB = tailB->next;
  14. ++lenB;//求出B的长度
  15. }
  16. if(tailA != tailB)//如果两个链表的尾不相等,则返回空
  17. {
  18. return NULL;
  19. }
  20. //相交,求交点,长的先走差距步,再同时找交点。
  21. struct ListNode* shortList = headA, *longList = headB;//默认A短B长
  22. if(lenA > lenB)
  23. {
  24. shortList = headB;
  25. longList = headA;
  26. }
  27. int gap = abs(lenA - lenB);//求出差距
  28. while(gap--)//减gap次,若是--gap,就是减了gap - 1
  29. {
  30. longList = longList->next;
  31. }
  32. while(shortList && longList)
  33. {
  34. if(shortList == longList)
  35. return shortList;//此时shortList == longList;
  36. longList = longList->next;
  37. shortList = shortList->next;
  38. }
  39. //最最关键的一步:就是要在外面返回一个值,因为编译器不会判断代码在哪返回,只会检查你的代码的语法问题。
  40. return NULL;
  41. }

总结

 

本文分别从双向链表的概念、实现,还比较了顺序表和链表的区别,以及5道链表oj题四个方面介绍了链表进阶的知识,希望大家读后能够有所收获。

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

闽ICP备14008679号