当前位置:   article > 正文

链表的详解

链表

目录

一、不带头单链表

       初始准备

       链表节点创建与销毁

       尾插尾删

       头插头删

       随机位置插入与删除

       缺陷

       全部代码

二、链表带环问题

三、带头双链表

       初始准备

       链表节点创建与销毁

       尾插尾删

       头插头删

       随机位置插入与删除

       全部代码

四、对比顺序表与链表


一、不带头单链表

初始准备

不带头单链表,首先创建了一个结构体,而该结构体有2个成员,一个用来存储数据(data),另一个用来存储下一个数据的地址(next*),从而使得两个结构体变量连接起来,如下图

为了书写简洁,用typedef将其结构体类型名修改

 

又为了能方便存储不同类型的数据,不至于把程序写的太呆板,采用typedef将其类型名修改,要存储其他类型的数据时,只要修改即可。如下图,存储浮点型数据时,只要int改为float或double

打印链表

每打印一个数据,指针就向后移动一步

 注意:打印因为没有改变链表,所以只需传一级指针即可

           上图也可以不用创建cur,直接用phead

指针传参

无头单链表中,只有打印和查找数据时,不用传二级指针,其余时候都要传二级,因为要改变链表

上述两种都是传递地址,在C语言中要改变实参时,必须传地址才行 

链表节点创建与销毁

先用malloc开辟一块空间,大小是结构体的大小,然后再做判断,最后再对成员进行初始化即可

链表销毁时,要采用两个指针,一个在前,一个在后,要不然释放空间后,就成了野指针,找不到后面的数据了,把最后一个数据销毁后,再将其置空即可

 

尾插尾删

首先判断链表是否为空(没有数据),没有就直接添加数据即可,有就找尾,最后一个数据的next指针为空,就表示找到了,直接连接数据紧接其后即可

要分两种情况,一种是只有一个节点时,另一种是多个节点时

多个节点时,尾删也需要两个指针,否则在删除最后一个数据后,要把前一个数据的next*置空,否则next*就是野指针了,下一次删除就找不到尾了

一个节点时,直接释放,置空即可

 

头插头删

头插时不需要判断链表是否为空,只要创建新节点,让它指向头节点,再把新节点的地址赋值给头节点的指针,作为新的头节点指针

 头删时要判断链表是否为空,毕竟空链表不能删除

先用一个指针变量存储头节点的下一个节点的地址,再将头节点销毁,再将其前面创建的指针的值赋给头节点的指针 

随机位置插入与删除

查找随机数,返回其地址,找不到就返回空指针,同样不需要传二级指针

在随机数的后面插入数据,找不到数据时,pos为空,所以断言判断一下。如下图,如果不多创建一个指针变量时,就需要先连线2,再连线1,因为先练线1时,就找不到cur2,指向的那个节点了;多创建一个变量保存节点地址,1、2那个先连都可以

 

在随机数的前面插入数据时,要分两种情况

第一种,随机数就是头节点的数据时,直接头插即可

第二种,找到pos的前一个节点,再创建节点,3个节点连接即可

在前面和后面插入两种方法对比,在后面插入明显更简单,效率也更高,因为不需要找前一个节点,这也是单链表的缺陷,双链表可以弥补这一缺陷

删除随机数后面的一节点的数据时,要作2次断言,第一个是随机数存不存在,第二个是随机数的下一个节点存不存在

 

删除随机数时,首先判断是不是随机数存不存在,同样有两种情况

第一种,随机数是头节点的数据时,直接头删即可

第二种,找到随机数的前一个节点,再连接,释放随机数的节点即可

缺陷:

每存一个数据,都要存一个指针去链接后面数据节点

不支持随机访问(用下标去访问第i个)

全部代码

  1. //头文件
  2. #pragma once
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<assert.h>
  6. typedef int DataType;
  7. typedef struct SList
  8. {
  9. DataType data;
  10. struct SList* next;
  11. }SLT;
  12. //创建节点
  13. SLT* SListNode(DataType x);
  14. //打印链表
  15. void SListPrint(SLT* phead);
  16. //销毁链表
  17. void SListDestroy(SLT** pphead);
  18. //尾插
  19. void SListPushBack(SLT** pphead, DataType x);
  20. //尾删
  21. void SListPopBack(SLT** pphead);
  22. //头插
  23. void SListPushFront(SLT** pphead, DataType x);
  24. //头删
  25. void SListPopFront(SLT** pphead);
  26. //查找随机数
  27. SLT* SListFind(SLT* pphead, DataType pos);
  28. //随机位置向后插入
  29. void SListInsert(SLT* pos, DataType x);
  30. //随机位置的数的删除
  31. void SListDeleteAfter(SLT* pos);
  32. //随机位置向前插入
  33. void SListInsertPrev(SLT** pphead, SLT* pos, DataType x);
  34. //随机位置数据的删除
  35. void SListDelete(SLT** pphead, SLT* pos);
  36. //定义文件
  37. #include"SList.h"
  38. SLT* SListNode(DataType x)
  39. {
  40. SLT* newnode = (SLT*)malloc(sizeof(SLT));
  41. if (newnode == NULL)
  42. {
  43. printf("malloc fail\n");
  44. exit(-1);
  45. }
  46. newnode->data = x;
  47. newnode->next = NULL;
  48. return newnode;
  49. }
  50. void SListPushBack(SLT** pphead, DataType x)
  51. {
  52. if (*pphead == NULL)
  53. {
  54. *pphead = SListNode(x);
  55. }
  56. else
  57. {
  58. SLT* cur = *pphead;
  59. while (cur->next)
  60. {
  61. cur = cur->next;
  62. }
  63. cur->next = SListNode(x);
  64. }
  65. }
  66. void SListPrint(SLT* phead)
  67. {
  68. SLT* cur = phead;
  69. while (cur)
  70. {
  71. printf("%d ", cur->data);
  72. cur = cur->next;
  73. }
  74. printf("\n");
  75. }
  76. void SListDestroy(SLT** pphead)
  77. {
  78. assert(pphead);
  79. SLT* cur = *pphead;
  80. SLT* prev = *pphead;
  81. while (cur)
  82. {
  83. prev = cur;
  84. cur = cur->next;
  85. free(prev);
  86. }
  87. *pphead = NULL;
  88. }
  89. void SListPopBack(SLT** pphead)
  90. {
  91. assert(*pphead);
  92. SLT* cur = *pphead;
  93. SLT* prev = NULL;
  94. if ((*pphead)->next == NULL)
  95. {
  96. free(*pphead);
  97. *pphead = NULL;
  98. }
  99. else
  100. {
  101. while (cur->next)
  102. {
  103. prev = cur;
  104. cur = cur->next;
  105. }
  106. free(cur);
  107. cur = NULL;
  108. prev->next = NULL;
  109. }
  110. }
  111. void SListPushFront(SLT** pphead, DataType x)
  112. {
  113. SLT* cur = *pphead;
  114. SLT* newnode = SListNode(x);
  115. newnode->next = cur;
  116. *pphead = newnode;
  117. }
  118. void SListPopFront(SLT** pphead)
  119. {
  120. assert(*pphead);
  121. SLT* cur = (*pphead)->next;
  122. free(*pphead);
  123. *pphead = cur;
  124. }
  125. SLT* SListFind(SLT* phead, DataType pos)
  126. {
  127. assert(phead);
  128. SLT* cur = phead;
  129. while (cur)
  130. {
  131. if (cur->data == pos)
  132. {
  133. return cur;
  134. }
  135. else
  136. {
  137. cur = cur->next;
  138. }
  139. }
  140. return NULL;
  141. }
  142. void SListInsert(SLT* pos, DataType x)
  143. {
  144. assert(pos);
  145. SLT* cur2 = pos->next;
  146. SLT* newnode = SListNode(x);
  147. newnode->next = cur2;
  148. pos->next = newnode;
  149. }
  150. void SListDelete(SLT** pphead, SLT* pos)
  151. {
  152. assert(pphead);
  153. assert(pos);
  154. if (pos == *pphead)
  155. {
  156. SListPopFront(pphead);
  157. }
  158. else
  159. {
  160. SLT* prev = *pphead;
  161. while (prev->next != pos)
  162. {
  163. prev = prev->next;
  164. }
  165. prev->next = pos->next;
  166. free(pos);
  167. pos = NULL;
  168. }
  169. }
  170. void SListDeleteAfter(SLT* pos)
  171. {
  172. assert(pos);
  173. assert(pos->next);
  174. pos->next = pos->next->next;
  175. free(pos->next);
  176. pos->next = NULL;
  177. }
  178. void SListInsertPrev(SLT** pphead, SLT* pos, DataType x)
  179. {
  180. SLT* newnode = SListNode(x);
  181. if (*pphead == pos)
  182. {
  183. newnode->next = *pphead;
  184. *pphead = newnode;
  185. }
  186. else
  187. {
  188. SLT* prev = *pphead;
  189. while (prev->next != pos)
  190. {
  191. prev = prev->next;
  192. }
  193. newnode->next = pos;
  194. prev->next = newnode;
  195. }
  196. }
  197. //测试文件
  198. #include"SList.h"
  199. void test1()
  200. {
  201. SLT* plist = NULL;
  202. SListPushBack(&plist, 1);
  203. SListPushBack(&plist, 2);
  204. SListPushBack(&plist, 3);
  205. SListPushBack(&plist, 4);
  206. SListPrint(plist);
  207. SListPopBack(&plist);
  208. SListPopBack(&plist);
  209. SListPopBack(&plist);
  210. SListPopBack(&plist);
  211. SListPopBack(&plist);
  212. SListPopBack(&plist);
  213. SListPopBack(&plist);
  214. SListPrint(plist);
  215. }
  216. void test2()
  217. {
  218. SLT* plist = NULL;
  219. SListPushFront(&plist, 1);
  220. SListPushFront(&plist, 2);
  221. SListPushFront(&plist, 3);
  222. SListPushFront(&plist, 4);
  223. SListPrint(plist);
  224. SListPopFront(&plist);
  225. SListPopFront(&plist);
  226. SListPopFront(&plist);
  227. SListPopFront(&plist);
  228. SListPrint(plist);
  229. }
  230. void test3()
  231. {
  232. SLT* plist = NULL;
  233. SListPushBack(&plist, 1);
  234. SListPushBack(&plist, 2);
  235. SListPushBack(&plist, 3);
  236. SListPushBack(&plist, 4);
  237. SLT* pos = SListFind(plist, 3);
  238. SListInsert(pos, 8);
  239. /*SListDeleteAfter(pos);
  240. SListPrint(plist);*/
  241. SListInsertPrev(&plist, pos, 15);
  242. SListPrint(plist);
  243. SListDelete(&plist, pos);
  244. SListPrint(plist);
  245. SListDestroy(&plist);
  246. }
  247. int main()
  248. {
  249. //test1();
  250. //test2();
  251. test3();
  252. return 0;
  253. }

二、链表带环问题

结论一:

快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,如果链表 带环则一定会在环中相遇

证明如下图:

 至于fast走4步,slow走1步的情况下,读者可依照上图依葫芦画瓢分析

结论二:

fast与slow一定会在环的入口处相遇

三、带头双链表

初始准备

首先创建一个结构体,有3个成员,第一个是存储数据,2个指针,一个指向前节点,另一个指向后节点,用typedef将其结构体类型名修改,使其简洁,方便使用

用typedef将其数据类型名修改,便于存储其它数据时,不用大幅度改动

链表节点创建与销毁

首先创建一个头节点,用malloc开辟空间,不存储数据,前后指针均指向本身

 

然后用malloc创建其它节点,添加节点时,只需调用函数即可,前后指针均置为NULL

销毁双链表时,从头节点后的节点开始销毁,最后再销毁头节点

打印双链表

尾插尾删

 

尾删不能把头节点给删了,所以需断言

 

头插头删

 

头删不能把头节点给删了,所以需断言

  

随机位置插入与删除

查找

找不到返回NULL,找得到返回其地址

 

 

 

全部代码

  1. //头文件
  2. #pragma once
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<assert.h>
  6. typedef int DataType;
  7. typedef struct List
  8. {
  9. DataType data;
  10. struct List* prev;
  11. struct List* next;
  12. }LT;
  13. //初始化双链表
  14. LT* LTInit();
  15. //创建节点
  16. LT* LTNode(DataType x);
  17. //打印双链表
  18. void LTPrint(LT* phead);
  19. //尾插
  20. void LTPushBack(LT* phead, DataType x);
  21. //尾删
  22. void LTPopBack(LT* phead);
  23. //头插
  24. void LTPushFront(LT* phead, DataType x);
  25. //头删
  26. void LTPopFront(LT* phead);
  27. //查找
  28. LT* LTFind(LT* phead, DataType pos);
  29. //随机位置插入数据
  30. void LTInsert(LT* pos, DataType x);
  31. //随机位置的数据删除
  32. void LTDelete(LT* pos);
  33. //销毁双链表
  34. void LTDestroy(LT* phead);
  35. //定义文件
  36. #include"List.h"
  37. LT* LTInit()
  38. {
  39. LT* head = (LT*)malloc(sizeof(LT));
  40. if (head == NULL)
  41. {
  42. printf("malloc fail\n");
  43. exit(-1);
  44. }
  45. head->next = head;
  46. head->prev = head;
  47. return head;
  48. }
  49. LT* LTNode(DataType x)
  50. {
  51. LT* newnode = (LT*)malloc(sizeof(LT));
  52. if (newnode == NULL)
  53. {
  54. printf("malloc fail\n");
  55. exit(-1);
  56. }
  57. newnode->data = x;
  58. newnode->prev = NULL;
  59. newnode->next = NULL;
  60. return newnode;
  61. }
  62. void LTPrint(LT* phead)
  63. {
  64. assert(phead);
  65. LT* cur = phead->next;
  66. while (cur != phead)
  67. {
  68. printf("%d ", cur->data);
  69. cur = cur->next;
  70. }
  71. printf("\n");
  72. }
  73. void LTPushBack(LT* phead, DataType x)
  74. {
  75. assert(phead);
  76. LT* newnode = LTNode(x);
  77. LT* tail = phead->prev;
  78. tail->next = newnode;
  79. newnode->prev = tail;
  80. newnode->next = phead;
  81. phead->prev = newnode;
  82. }
  83. void LTPopBack(LT* phead)
  84. {
  85. assert(phead);
  86. assert(phead->next != phead);
  87. LT* tail = phead->prev;
  88. LT* prevtail = phead->prev->prev;
  89. phead->prev = prevtail;
  90. prevtail->next = phead;
  91. free(tail);
  92. tail = NULL;
  93. }
  94. void LTPushFront(LT* phead, DataType x)
  95. {
  96. assert(phead);
  97. LT* newnode = LTNode(x);
  98. LT* begintail = phead->next;
  99. phead->next = newnode;
  100. newnode->prev = phead;
  101. begintail->prev = newnode;
  102. newnode->next = begintail;
  103. }
  104. void LTPopFront(LT* phead)
  105. {
  106. assert(phead);
  107. assert(phead->next != phead);
  108. LT* begin = phead->next;
  109. LT* cur = begin->next;
  110. phead->next = cur;
  111. cur->prev = phead;
  112. free(begin);
  113. begin = NULL;
  114. }
  115. LT* LTFind(LT* phead, DataType pos)
  116. {
  117. assert(phead);
  118. LT* cur = phead->next;
  119. while (cur != phead)
  120. {
  121. if (cur->data == pos)
  122. {
  123. return cur;
  124. }
  125. else
  126. {
  127. cur = cur->next;
  128. }
  129. }
  130. return NULL;
  131. }
  132. void LTInsert(LT* pos, DataType x)
  133. {
  134. assert(pos);
  135. LT* newnode = LTNode(x);
  136. LT* prevpos = pos->prev;
  137. prevpos->next = newnode;
  138. newnode->prev = prevpos;
  139. pos->prev = newnode;
  140. newnode->next = pos;
  141. }
  142. void LTDelete(LT* pos)
  143. {
  144. assert(pos);
  145. LT* prevpos = pos->prev;
  146. LT* tailpos = pos->next;
  147. prevpos->next = tailpos;
  148. tailpos->prev = prevpos;
  149. free(pos);
  150. pos = NULL;
  151. }
  152. void LTDestroy(LT* phead)
  153. {
  154. assert(phead);
  155. LT* cur = phead->next;
  156. while (cur != phead)
  157. {
  158. LT* next = cur->next;
  159. free(cur);
  160. cur = next;
  161. }
  162. free(phead);
  163. phead = NULL;
  164. }
  165. //测试文件
  166. #include"List.h"
  167. void test1()
  168. {
  169. LT* plist = LTInit();
  170. LTPushBack(plist, 1);
  171. LTPushBack(plist, 2);
  172. LTPushBack(plist, 3);
  173. LTPushBack(plist, 4);
  174. LTPushBack(plist, 5);
  175. LTPushBack(plist, 6);
  176. LTPushBack(plist, 7);
  177. LTPrint(plist);
  178. LTPopBack(plist);
  179. LTPopBack(plist);
  180. LTPopBack(plist);
  181. LTPopBack(plist);
  182. //LTPopBack(plist);
  183. //LTPopBack(plist);
  184. //LTPopBack(plist);
  185. //LTPopBack(plist);
  186. LTPrint(plist);
  187. }
  188. void test2()
  189. {
  190. LT* plist = LTInit();
  191. LTPushFront(plist, 1);
  192. LTPushFront(plist, 2);
  193. LTPushFront(plist, 3);
  194. LTPushFront(plist, 4);
  195. LTPushFront(plist, 5);
  196. LTPushFront(plist, 6);
  197. LTPushFront(plist, 7);
  198. LTPrint(plist);
  199. LTPopFront(plist);
  200. LTPopFront(plist);
  201. LTPopFront(plist);
  202. LTPopFront(plist);
  203. LTPopFront(plist);
  204. //LTPopFront(plist);
  205. //LTPopFront(plist);
  206. //LTPopFront(plist);
  207. LTPrint(plist);
  208. }
  209. void test3()
  210. {
  211. LT* plist = LTInit();
  212. LTPushBack(plist, 1);
  213. LTPushBack(plist, 2);
  214. LTPushBack(plist, 3);
  215. LTPushBack(plist, 4);
  216. LT* pos = LTFind(plist, 3);
  217. LTInsert(pos, 8);
  218. LTInsert(pos, 16);
  219. LTDelete(pos);
  220. LTPrint(plist);
  221. LTDestroy(plist);
  222. }
  223. int main()
  224. {
  225. test1();
  226. //test2();
  227. //test3();
  228. return 0;
  229. }

四、对比顺序表与链表

顺序表

优点:

支持随机访问。需要随机访问结构支持算法可以很好的适用

缺点:

头部中部插入删除时间效率低  O(n)

连续的物理空间,空间不够了以后需要增容:

增容有一定程度消耗

为了避免频繁增容,一般我们都按倍数去增容,用不完可能存在一定的空间浪费

链表(双向带头循环)

优点:

任意位置插入删除效率高  O(1)

按需申请释放空间

缺点:

不支持随机访问(用下标访问)。意味着:一些排序、二分查找等在这种结构上不适用

链表每存储一个值,同时要存储一个指针,也有一定的消耗

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

闽ICP备14008679号