当前位置:   article > 正文

【数据结构】第三站:单链表_listnode单链表

listnode单链表

目录

一、顺序表的缺陷

二、链表

1.链表的概念以及结构

2.链表的分类

3.单链表的逻辑结构与物理结构

三、单链表的实现

1.单链表的定义

2.单链表的接口定义

3.单链表的接口实现

四、单链表的实现完整代码


一、顺序表的缺陷

在上一篇文章中,我们了解了顺序表的结构以及他的接口的实现。但同时我们也发现了他的一些缺陷

问题:
1. 中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?下面给出了链表的结构来看看

二、链表

1.链表的概念以及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的

2.链表的分类

链表一共有八种类型,他们可由是单向还是双向,是循环还是非循环,是带头结点还是不带头结点进行排列组合出八种结构

虽然有很多种结构,但是只有两种最为常用

无头单向非循环链表和带头双向循环链表

这里我们先只需要了解无头单向非循环链表,其他链表后续了解

3.单链表的逻辑结构与物理结构

如下图所示,是链表的实际的物理结构与逻辑结构。物理结构就是实实在在数据在内存中的变化,逻辑结构就是为了方便理解,形象化出来的

三、单链表的实现

1.单链表的定义

  1. typedef int SLTDateType;
  2. typedef struct SListNode
  3. {
  4. SLTDateType data;
  5. struct SListNode* next;
  6. }SListNode;

如上代码所示,单链表有数据域和指针域两部分组成,指针是用于指向下一个结点的指针

2.单链表的接口定义

  1. //单链表的打印
  2. void SListPrint(SListNode* plist);
  3. //单链表的尾插
  4. void SListPushBack(SListNode** pplist, SLTDateType x);
  5. //单链表的头插
  6. void SListPushFront(SListNode** pplist, SLTDateType x);
  7. //单链表的尾删
  8. void SListPopBack(SListNode** pplist);
  9. //单链表的头删
  10. void SListPopFront(SListNode** pplist);
  11. //单链表的查找
  12. SListNode* SListFind(SListNode* plist,SLTDateType x);
  13. //单链表在pos位置之后插入
  14. void SListInsertAfter(SListNode* pos, SLTDateType x);
  15. //单链表在pos位置之前插入
  16. void SListInsertPrev(SListNode** pplist, SListNode* pos, SLTDateType x);
  17. //单链表在pos之后删除
  18. void SListEraseAfter(SListNode* pos);
  19. //单链表在pos之前删除
  20. void SListErasePrev(SListNode** pplist, SListNode* pos);
  21. //单链表在pos位置删除
  22. void SListErase(SListNode** pplist, SListNode* pos);
  23. //单链表的销毁
  24. void SListDestroy(SListNode** pplist);

如上代码所示,是我们的单链表需要实现的接口,对于链表和顺序表一样都是为了实现数据的管理,区别就是前者是离散的,后者是连续的。我们的目的还是增删查改

3.单链表的接口实现

1.单链表的打印

  1. //单链表的打印
  2. void SListPrint(SListNode* plist)
  3. {
  4. SListNode* cur = plist;
  5. while (cur != NULL)
  6. {
  7. printf("%d->", cur->data);
  8. cur = cur->next;
  9. }
  10. printf("NULL\n");
  11. }

我们先来完成单链表的打印,对于单链表的打印,还是比较简单的,只需要先将头结点的指针给保存下来,然后依次去遍历单链表即可

2.单链表的尾插以及获取结点函数

  1. //获取一个结点
  2. SListNode* BuySListNode(SLTDateType x)
  3. {
  4. SListNode* tmp = (SListNode*)malloc(sizeof(SListNode));
  5. if (tmp == NULL)
  6. {
  7. perror("malloc fail");
  8. return;
  9. }
  10. tmp->next = NULL;
  11. tmp->data = x;
  12. return tmp;
  13. }
  14. //单链表的尾插
  15. void SListPushBack(SListNode** pplist, SLTDateType x)
  16. {
  17. assert(pplist);
  18. SListNode* newnode = BuySListNode(x);
  19. if (*pplist == NULL)
  20. {
  21. *pplist = newnode;
  22. return;
  23. }
  24. SListNode* tail = (*pplist);
  25. while (tail->next != NULL)
  26. {
  27. tail = tail->next;
  28. }
  29. tail->next = newnode;
  30. }

对于单链表的尾插,我们要特别注意了。pplist是单链表头结点的地址,所以一定不为空

首先是获取结点,我们为了方便,先直接将其封装为一个函数。

有了结点了,那么我们还要思考如何尾插,那么我们先完成一般情况,假设已经有了一个很长的单链表了,我们还想要继续尾插一个值,那么只需要先找到原来的尾结点,然后将尾结点和新结点进行链接即可

当然还是存在一些特殊情况的,比如说原来的单链表压根就没有尾结点,也就是说链表是空的,那么上面的一般方法肯定行不通,这里其实就需要特殊处理一下,直接将新节点和链表头链接起来即可

3.单链表的头插

  1. //单链表的头插
  2. void SListPushFront(SListNode** pplist, SLTDateType x)
  3. {
  4. assert(pplist);
  5. SListNode* newnode = BuySListNode(x);
  6. SListNode* first = *pplist;
  7. *pplist = newnode;
  8. newnode->next = first;
  9. }

对于单链表的头插,就比较简单了,我们直接创建一个新结点,然后记录下原来的第一个结点的地址,然后让链表头与新节点链接起来,然后新节点与原来的第一个结点链接起来,这里我们会发下,其实是不需要处理空链表的情况的,这里体现了单链表适合头插

4.单链表的尾删

  1. //单链表的尾删
  2. void SListPopBack(SListNode** pplist)
  3. {
  4. assert(pplist);
  5. assert(*pplist);
  6. if ((*pplist)->next == NULL)
  7. {
  8. free(*pplist);
  9. *pplist = NULL;
  10. }
  11. else
  12. {
  13. SListNode* tail = *pplist;
  14. while (tail->next->next != NULL)
  15. {
  16. tail = tail->next;
  17. }
  18. free(tail->next);
  19. tail->next = NULL;
  20. }
  21. }

对于单链表的尾删,我们要想清楚了,首先是一般情况,当链表很长的时候,我们想要删除最后一个结点,那么得先找到前一个结点,然后释放最后一个结点,最后让前一个结点指向空。

我们还需要注意链表为空的状态,这个肯定是不可以删除的,所以我们直接断言掉。

然后是链表为一个结点的情况,如果链表只有一个结点,那么我们会发现,压根找不到前一个结点,所以我们也特殊处理,我们直接释放掉第一个结点,然后置空即可。

5.单链表的头删

  1. //单链表的头删
  2. void SListPopFront(SListNode** pplist)
  3. {
  4. assert(pplist);
  5. assert(*pplist);
  6. SListNode* second = (*pplist)->next;
  7. free(*pplist);
  8. *pplist = second;
  9. }

对于单链表的头删,我们同样断言掉链表为空的状态

然后我们需要做的就是记录第二个结点,然后释放原来的第一个结点,最后连接链表头和第二个结点。这样我们就实现了我们的目的。值得注意的是,我们发现链表的头删也是比较有优势的。

6.单链表的查找

  1. //单链表的查找
  2. SListNode* SListFind(SListNode* plist,SLTDateType x)
  3. {
  4. SListNode* cur = plist;
  5. while (cur != NULL)
  6. {
  7. if (cur->data == x)
  8. {
  9. return cur;
  10. }
  11. cur = cur->next;
  12. }
  13. return NULL;
  14. }

对于单链表的查找,这个也很简单,他和单链表的打印思路是一样的,不同的是只需要返回结点的地址即可。

7.单链表在pos位置之后的插入

  1. //单链表在pos位置之后插入
  2. void SListInsertAfter(SListNode* pos, SLTDateType x)
  3. {
  4. assert(pos);
  5. SListNode* newnode = BuySListNode(x);
  6. SListNode* next = pos->next;
  7. pos->next = newnode;
  8. newnode->next = next;
  9. }

单链表在pos位置之后的插入也是比较简单的,我们只需要先申请一个结点,然后记录pos位置的下一个结点,然后连接就可以了。值得注意的是,pos的位置不可能为空。因为他不是一个有效的结点地址

8.单链表在pos位置之前的插入

  1. //单链表在pos位置之前插入
  2. void SListInsertPrev(SListNode** pplist, SListNode* pos, SLTDateType x)
  3. {
  4. assert(pplist);
  5. assert(pos);
  6. assert(*pplist);
  7. SListNode* newnode = BuySListNode(x);
  8. if (*pplist == pos)
  9. {
  10. *pplist = newnode;
  11. newnode->next = pos;
  12. }
  13. else
  14. {
  15. SListNode* prev = *pplist;
  16. while (prev->next != pos)
  17. {
  18. prev = prev->next;
  19. }
  20. prev->next = newnode;
  21. newnode->next = pos;
  22. }
  23. }

对于在pos位置之前的插入,确实是比较繁琐了。我们要思考,首先pos和pplist不可能为空,然后这个链表也是不可能为空链表的,至少也要有一个值。否则如果存在pos这个结点呢?

然后我们在来考虑一般情况,我们假设链表很长,在中间位置pos之前插入一个结点,那么毫无疑问的是,我们需要先申请一个结点,然后在通过遍历的方式要找到pos之前的那个结点。

有了这两个结点,那么我们就可以进行连接了。

然后是特殊情况,假如这个链表只有一个结点呢,这个结点正好就是pos,我们发现pos就没有前一个结点,其实这个就等效于头插。我们采用头插的方式即可

9.单链表在pos之后删除

  1. //单链表在pos之后删除
  2. void SListEraseAfter(SListNode* pos)
  3. {
  4. assert(pos);
  5. assert(pos->next);
  6. SListNode* next = pos->next;
  7. pos->next = next->next;
  8. free(next);
  9. next = NULL;
  10. }

单链表在pos之后删除的话,首先pos和pos的下一个结点不会为空,否则题目就矛盾了。所以我们得断言,然后我们直接记录pos 的下一个结点,然后连接pos和pos之后的结点。最后释放掉pos的下一个结点即可。

10.单链表在pos之前删除

  1. //单链表在pos之前删除
  2. void SListErasePrev(SListNode** pplist, SListNode* pos)
  3. {
  4. assert(pos);
  5. assert(pplist);
  6. assert(pos != *pplist);
  7. assert(*pplist);
  8. if ((*pplist)->next == pos)
  9. {
  10. SListNode* del = *pplist;
  11. *pplist = pos;
  12. free(del);
  13. del = NULL;
  14. }
  15. else
  16. {
  17. SListNode* prev = *pplist;
  18. while (prev->next->next != pos)
  19. {
  20. prev = prev->next;
  21. }
  22. SListNode* del = prev->next;
  23. prev->next = pos;
  24. free(del);
  25. del = NULL;
  26. }
  27. }

对于单链表在pos之前删除确实就比较复杂了,首先pos,pplist,*pplist肯定不可能为空,然后pos也绝不可以是头节点,所以pos!=*pplist

我们现在来思考,假设一般情况,链表很长,在中间位置是pos,删除pos的前一个结点,那么我们就需要找到pos 的前一个的前一个结点。然后记录pos的前一个结点。释放pos 的前一个结点,然后进行连接即可

对于特殊情况,也就是,pos在第二个结点上,这样我们无法找到pos的前一个的前一个结点,但是这个就是头删,我们直接采用类似的思路即可

11.单链表在pos位置的删除

  1. //单链表在pos位置删除
  2. void SListErase(SListNode** pplist, SListNode* pos)
  3. {
  4. assert(pplist);
  5. assert(pos);
  6. assert(*pplist);
  7. if (*pplist == pos)
  8. {
  9. free(pos);
  10. *pplist = pos = NULL;
  11. }
  12. else
  13. {
  14. SListNode* prev = *pplist;
  15. while (prev->next != pos)
  16. {
  17. prev = prev->next;
  18. }
  19. prev->next = pos->next;
  20. free(pos);
  21. pos = NULL;
  22. }
  23. }

这个与上一个接口是基本一致的思路。不同的是,pos可以在头结点了,如果是头节点就是头删。

如果不是,就是先找到前一个结点,然后进行删除连接即可

12.单链表的销毁

  1. //单链表的销毁
  2. void SListDestroy(SListNode** pplist)
  3. {
  4. SListNode* cur = *pplist;
  5. while (cur != NULL)
  6. {
  7. SListNode* next = cur->next;
  8. free(cur);
  9. cur = next;
  10. }
  11. }

对于单链表的销毁,这个也很简单,就直接遍历销毁即可,与打印和查找的思路是一致的

四、单链表的实现完整代码

SList.h文件

  1. #pragma once
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<malloc.h>
  5. #include<assert.h>
  6. typedef int SLTDateType;
  7. typedef struct SListNode
  8. {
  9. SLTDateType data;
  10. struct SListNode* next;
  11. }SListNode;
  12. //单链表的打印
  13. void SListPrint(SListNode* plist);
  14. //单链表的尾插
  15. void SListPushBack(SListNode** pplist, SLTDateType x);
  16. //单链表的头插
  17. void SListPushFront(SListNode** pplist, SLTDateType x);
  18. //单链表的尾删
  19. void SListPopBack(SListNode** pplist);
  20. //单链表的头删
  21. void SListPopFront(SListNode** pplist);
  22. //单链表的查找
  23. SListNode* SListFind(SListNode* plist,SLTDateType x);
  24. //单链表在pos位置之后插入
  25. void SListInsertAfter(SListNode* pos, SLTDateType x);
  26. //单链表在pos位置之前插入
  27. void SListInsertPrev(SListNode** pplist, SListNode* pos, SLTDateType x);
  28. //单链表在pos之后删除
  29. void SListEraseAfter(SListNode* pos);
  30. //单链表在pos之前删除
  31. void SListErasePrev(SListNode** pplist, SListNode* pos);
  32. //单链表在pos位置删除
  33. void SListErase(SListNode** pplist, SListNode* pos);
  34. //单链表的销毁
  35. void SListDestroy(SListNode** pplist);

SList.c文件

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"SList.h"
  3. //单链表的打印
  4. void SListPrint(SListNode* plist)
  5. {
  6. SListNode* cur = plist;
  7. while (cur != NULL)
  8. {
  9. printf("%d->", cur->data);
  10. cur = cur->next;
  11. }
  12. printf("NULL\n");
  13. }
  14. //获取一个结点
  15. SListNode* BuySListNode(SLTDateType x)
  16. {
  17. SListNode* tmp = (SListNode*)malloc(sizeof(SListNode));
  18. if (tmp == NULL)
  19. {
  20. perror("malloc fail");
  21. return;
  22. }
  23. tmp->next = NULL;
  24. tmp->data = x;
  25. return tmp;
  26. }
  27. //单链表的尾插
  28. void SListPushBack(SListNode** pplist, SLTDateType x)
  29. {
  30. assert(pplist);
  31. SListNode* newnode = BuySListNode(x);
  32. if (*pplist == NULL)
  33. {
  34. *pplist = newnode;
  35. return;
  36. }
  37. SListNode* tail = (*pplist);
  38. while (tail->next != NULL)
  39. {
  40. tail = tail->next;
  41. }
  42. tail->next = newnode;
  43. }
  44. //单链表的头插
  45. void SListPushFront(SListNode** pplist, SLTDateType x)
  46. {
  47. assert(pplist);
  48. SListNode* newnode = BuySListNode(x);
  49. SListNode* first = *pplist;
  50. *pplist = newnode;
  51. newnode->next = first;
  52. }
  53. //单链表的尾删
  54. void SListPopBack(SListNode** pplist)
  55. {
  56. assert(pplist);
  57. assert(*pplist);
  58. if ((*pplist)->next == NULL)
  59. {
  60. free(*pplist);
  61. *pplist = NULL;
  62. }
  63. else
  64. {
  65. SListNode* tail = *pplist;
  66. while (tail->next->next != NULL)
  67. {
  68. tail = tail->next;
  69. }
  70. free(tail->next);
  71. tail->next = NULL;
  72. }
  73. }
  74. //单链表的头删
  75. void SListPopFront(SListNode** pplist)
  76. {
  77. assert(pplist);
  78. assert(*pplist);
  79. SListNode* second = (*pplist)->next;
  80. free(*pplist);
  81. *pplist = second;
  82. }
  83. //单链表的查找
  84. SListNode* SListFind(SListNode* plist,SLTDateType x)
  85. {
  86. SListNode* cur = plist;
  87. while (cur != NULL)
  88. {
  89. if (cur->data == x)
  90. {
  91. return cur;
  92. }
  93. cur = cur->next;
  94. }
  95. return NULL;
  96. }
  97. //单链表在pos位置之后插入
  98. void SListInsertAfter(SListNode* pos, SLTDateType x)
  99. {
  100. assert(pos);
  101. SListNode* newnode = BuySListNode(x);
  102. SListNode* next = pos->next;
  103. pos->next = newnode;
  104. newnode->next = next;
  105. }
  106. //单链表在pos位置之前插入
  107. void SListInsertPrev(SListNode** pplist, SListNode* pos, SLTDateType x)
  108. {
  109. assert(pplist);
  110. assert(pos);
  111. assert(*pplist);
  112. SListNode* newnode = BuySListNode(x);
  113. if (*pplist == pos)
  114. {
  115. *pplist = newnode;
  116. newnode->next = pos;
  117. }
  118. else
  119. {
  120. SListNode* prev = *pplist;
  121. while (prev->next != pos)
  122. {
  123. prev = prev->next;
  124. }
  125. prev->next = newnode;
  126. newnode->next = pos;
  127. }
  128. }
  129. //单链表在pos之后删除
  130. void SListEraseAfter(SListNode* pos)
  131. {
  132. assert(pos);
  133. assert(pos->next);
  134. SListNode* next = pos->next;
  135. pos->next = next->next;
  136. free(next);
  137. next = NULL;
  138. }
  139. //单链表在pos之前删除
  140. void SListErasePrev(SListNode** pplist, SListNode* pos)
  141. {
  142. assert(pos);
  143. assert(pplist);
  144. assert(pos != *pplist);
  145. assert(*pplist);
  146. if ((*pplist)->next == pos)
  147. {
  148. SListNode* del = *pplist;
  149. *pplist = pos;
  150. free(del);
  151. del = NULL;
  152. }
  153. else
  154. {
  155. SListNode* prev = *pplist;
  156. while (prev->next->next != pos)
  157. {
  158. prev = prev->next;
  159. }
  160. SListNode* del = prev->next;
  161. prev->next = pos;
  162. free(del);
  163. del = NULL;
  164. }
  165. }
  166. //单链表在pos位置删除
  167. void SListErase(SListNode** pplist, SListNode* pos)
  168. {
  169. assert(pplist);
  170. assert(pos);
  171. assert(*pplist);
  172. if (*pplist == pos)
  173. {
  174. free(pos);
  175. *pplist = pos = NULL;
  176. }
  177. else
  178. {
  179. SListNode* prev = *pplist;
  180. while (prev->next != pos)
  181. {
  182. prev = prev->next;
  183. }
  184. prev->next = pos->next;
  185. free(pos);
  186. pos = NULL;
  187. }
  188. }
  189. //单链表的销毁
  190. void SListDestroy(SListNode** pplist)
  191. {
  192. SListNode* cur = *pplist;
  193. while (cur != NULL)
  194. {
  195. SListNode* next = cur->next;
  196. free(cur);
  197. cur = next;
  198. }
  199. }

Test.c文件

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"Slist.h"
  3. void TestSList1()
  4. {
  5. SListNode* phead = NULL;
  6. SListPushBack(&phead, 1);
  7. SListPushBack(&phead, 2);
  8. SListPushBack(&phead, 3);
  9. SListPushBack(&phead, 4);
  10. SListPushBack(&phead, 5);
  11. SListPrint(phead);
  12. SListPushFront(&phead, 6);
  13. SListPushFront(&phead, 7);
  14. SListPushFront(&phead, 8);
  15. SListPushFront(&phead, 9);
  16. SListPushFront(&phead, 10);
  17. SListPrint(phead);
  18. SListPopBack(&phead);
  19. SListPopBack(&phead);
  20. SListPopBack(&phead);
  21. SListPopBack(&phead);
  22. SListPrint(phead);
  23. SListPopBack(&phead);
  24. SListPrint(phead);
  25. }
  26. void TestSList2()
  27. {
  28. SListNode* phead = NULL;
  29. SListPushBack(&phead, 1);
  30. SListPushBack(&phead, 2);
  31. SListPushBack(&phead, 3);
  32. SListPushBack(&phead, 4);
  33. SListPushBack(&phead, 5);
  34. SListPrint(phead);
  35. SListPopFront(&phead);
  36. SListPopFront(&phead);
  37. SListPopFront(&phead);
  38. SListPopFront(&phead);
  39. SListPrint(phead);
  40. SListPopFront(&phead);
  41. SListPrint(phead);
  42. }
  43. void TestSList3()
  44. {
  45. SListNode* phead = NULL;
  46. SListPushBack(&phead, 1);
  47. SListPushBack(&phead, 2);
  48. SListPushBack(&phead, 3);
  49. SListPushBack(&phead, 4);
  50. SListPushBack(&phead, 5);
  51. SListPrint(phead);
  52. SListNode* pos = SListFind(phead, 3);
  53. pos->data = 100;
  54. SListPrint(phead);
  55. SListInsertAfter(pos, 200);
  56. SListInsertAfter(pos, 300);
  57. SListInsertAfter(pos, 400);
  58. SListInsertAfter(pos, 500);
  59. SListPrint(phead);
  60. SListNode* pos2 = SListFind(phead, 1);
  61. SListInsertPrev(&phead, pos2, 101);
  62. SListInsertPrev(&phead, pos2, 102);
  63. SListInsertPrev(&phead, pos2, 103);
  64. SListInsertPrev(&phead, pos2, 104);
  65. SListPrint(phead);
  66. SListEraseAfter(pos2);
  67. SListEraseAfter(pos2);
  68. SListEraseAfter(pos2);
  69. SListPrint(phead);
  70. }
  71. void TestSList4()
  72. {
  73. SListNode* phead = NULL;
  74. SListPushBack(&phead, 1);
  75. SListPushBack(&phead, 2);
  76. SListPushBack(&phead, 3);
  77. SListPushBack(&phead, 4);
  78. SListPushBack(&phead, 5);
  79. SListPrint(phead);
  80. SListNode* pos = SListFind(phead, 5);
  81. SListErasePrev(&phead, pos);
  82. SListPrint(phead);
  83. SListErasePrev(&phead, pos);
  84. SListPrint(phead);
  85. SListErase(&phead, pos);
  86. SListPrint(phead);
  87. SListDestroy(&phead);
  88. }
  89. int main()
  90. {
  91. //TestSList1();
  92. //TestSList2();
  93. //TestSList3();
  94. TestSList4();
  95. return 0;
  96. }


本节内容到此位置,感谢您的阅读

如果对你有帮助的话,不要忘记点赞加收藏哦!!!

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/516347
推荐阅读
相关标签
  

闽ICP备14008679号