当前位置:   article > 正文

双向 链表

双向 链表

目录

一、双向链表的实现

二、顺序表和带头双向循环链表的区别


愿你熬过万丈孤苦,藏下星辰大海。


一、双向链表的实现

带头、双向、循环   

头部的prev指向尾部,

List.h

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

List.c

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include "List.h"
  3. LTNode* BuyLTNode(LTDataType x)
  4. {
  5. LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  6. if (newnode == NULL)
  7. {
  8. printf("malloc fail\n");
  9. exit(-1);
  10. }
  11. newnode->prev = NULL;
  12. newnode->data = x;
  13. newnode->next = NULL;
  14. }
  15. 初始化,二级指针的思路;
  16. //void ListInit(LTNode** pphead)//初始化,next指向自己,prev指向自己,才算是循环,头的地址还不清楚//初始化个哨兵位
  17. //{
  18. // assert(pphead);
  19. // *pphead = BuyLTNode(0);
  20. // (*pphead)->prev = *pphead;
  21. // (*pphead)->next = *pphead;
  22. //}
  23. //初始化,一级指针的思路,返回头即可,既可以改变头的地址,又可以传一级指针
  24. LTNode* ListInit(LTNode* phead)
  25. {
  26. phead = BuyLTNode(0);
  27. phead->prev = phead;
  28. phead->next = phead;
  29. return phead;
  30. }
  31. //打印
  32. void ListPrint(LTNode* phead)
  33. {
  34. //从head的next = cur->data开始打印,当cur->next = head结束
  35. assert(phead);
  36. LTNode* cur = phead->next;
  37. while (cur != phead)
  38. {
  39. printf("%d ", cur->data);
  40. cur = cur->next;
  41. }
  42. printf("\n");
  43. }
  44. void ListPushBack(LTNode* phead, LTDataType x)
  45. {
  46. assert(phead);//带头
  47. ListInsert(phead, x);
  48. /*LTNode* tail = phead->prev;
  49. LTNode* newnode = BuyLTNode(x);
  50. tail->next = newnode;
  51. newnode->prev = tail;
  52. newnode->next = phead;
  53. phead->prev = newnode;*/
  54. }
  55. //尾删,尾删到没有节点,也不用处理
  56. void ListPopBack(LTNode* phead)
  57. {
  58. assert(phead);
  59. //链表为空
  60. assert(phead->next != phead);
  61. /*LTNode* tail = phead->prev;
  62. LTNode* tailPrev = tail->prev;
  63. phead->prev = tailPrev;
  64. tailPrev->next = phead;
  65. free(tail);
  66. tail = NULL;*/
  67. ListErase(phead->prev);
  68. }
  69. //头插
  70. void ListPushFront(LTNode* phead, LTDataType x)
  71. {
  72. assert(phead);
  73. ListInsert(phead->next, x);
  74. /*LTNode* newnode = BuyLTNode(x);
  75. LTNode* next = phead->next;
  76. next->prev = newnode;
  77. newnode->next = next;
  78. phead->next = newnode;
  79. newnode->prev = phead;*/
  80. }
  81. //头删
  82. void ListPopFront(LTNode* phead)
  83. {
  84. assert(phead);
  85. ListErase(phead->next);
  86. /*assert(phead->next != phead);
  87. LTNode* head = phead->next;
  88. LTNode* next = head->next;
  89. phead->next = next;
  90. next->prev = phead;
  91. free(head);
  92. head = NULL;*/
  93. }
  94. //查找
  95. LTNode* ListFind(LTNode* phead, LTDataType x)
  96. {
  97. assert(phead);
  98. LTNode* cur = phead->next;
  99. while (cur != phead)
  100. {
  101. if (cur->data == x)
  102. {
  103. return cur;
  104. }
  105. cur = cur->next;
  106. }
  107. return NULL;
  108. }
  109. //插入
  110. void ListInsert(LTNode* pos, LTDataType x)
  111. {
  112. assert(pos);
  113. LTNode* newnode = BuyLTNode(x);
  114. LTNode* prev = pos->prev;
  115. prev->next = newnode;
  116. newnode->prev = prev;
  117. newnode->next = pos;
  118. pos->prev = newnode;
  119. }
  120. //删除
  121. void ListErase(LTNode* pos)//在主函数中,pos的实参要置空,否则实参就会成为野指针
  122. {
  123. assert(pos);
  124. LTNode* next = pos->next;
  125. LTNode* prev = pos->prev;
  126. prev->next = next;
  127. next->prev = prev;
  128. free(pos);
  129. pos = NULL;
  130. }
  131. //传一级指针是为了保持接口一致性
  132. void ListDestory(LTNode* phead)//注意,phead的实参要进行free,否则会导致野指针
  133. {
  134. assert(phead);
  135. LTNode* cur = phead->next;
  136. while (cur != phead)
  137. {
  138. LTNode* next = phead->next;
  139. free(cur);
  140. cur = next;
  141. }
  142. free(phead);
  143. phead = NULL;
  144. }

(1)首先进行哨兵位的初始化,哨兵位进行初始化之后,地址就不会再发生变化,所以在进行头插、尾插时,不需要传二级指针,仅仅需要哨兵位即可【尾插、头插也是在哨兵位之后】

(2)改变的是结构体的地址,改变的是指针的内容,改变指针的内容,需要传递指针的地址。

二、顺序表和带头双向循环链表的区别

这两个结构是相辅相成的结构,

顺序表优点:(1)物理空间是连续的,方便用下标随机访问。【二分查找、排序】

(2)CPU高速缓存命中率会更高。

缺点:(1)由于需要物理空间连续,空间不够需要扩容。扩容本身有一定的消耗,其次扩容机制还存在一定的空间消耗。(2)头部或者中间的插入删除,需要挪动数据,效率低。O(N)

链表优点(1)按需申请释放空间(2)任意位置可以O(1)的插入删除数据

缺点:不支持下标的随机访问,有些算法不适合在他上面进行。如:二分查找、排序等

编译连接之后,生成可执行程序,CPU执行这个程序,CPU要去访问内存,CPU不会直接访问内存,CPU嫌弃内存速度太慢,会把数据加载到三级缓存或者寄存器。4或者8byte小数据放到寄存器,大数据放到缓存。CPU会看数据是否在缓存,在的话就命中,直接访问,不在的话,先把数据从内存加载到缓存,再访问。会缓存一段连续的空间,所以顺序表命中更高。

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

闽ICP备14008679号