• 数据结构·单链表


    目录

    引子

    顺序表的问题

    单链表

    SList.h

    SList.c

    Test.c

    结束语 


     

    引子

    顺序表的问题

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

    因此为了弥补这一缺点引入了单链表的形式 -- 可以一定程度上解决顺序表的问题 

    下面给出了链表的结构

    实际上是地址指向 -- 这里的箭头是为了方便理解,实际并不存在

    添加一个数据就开一点空间

    单链表

    SList.h

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. typedef int SLTDataType;
    6. typedef struct SListNode
    7. {
    8. SLTDataType data;
    9. struct SListNode* next; //利用 next 来指向下一个节点
    10. }SLTNode;
    11. //创立一个节点(开辟新空间) -- 返回开辟节点的地址
    12. SLTNode* BuySLTNode(SLTDataType x);
    13. //打印
    14. void SListPrint(SLTNode *phead);
    15. //头插
    16. void SListPushFront(SLTNode **phead,SLTDataType x);
    17. //尾插
    18. void SListPushBack(SLTNode** phead, SLTDataType x);
    19. //头删
    20. void SListPopFront(SLTNode** pphead);
    21. //尾删
    22. void SListPopBack(SLTNode** pphead);
    23. //链表的查找
    24. SLTNode* SListFind(SLTNode* phead, SLTDataType x);
    25. //某个位置插入 -- 这里就不再用下标了 -- 默认在pos之前插入 -- 因为库函数一般都是如此,当然后插要简单的多
    26. void SListInsert(SLTNode** pphead, SLTNode *pos, SLTDataType x);
    27. //某个位置插入(在节点后面插入) -- 这个容易实现
    28. void SListInsertAfter(SLTNode* pos, SLTDataType x);
    29. //某个位置删除pos -- pos当前位置删除
    30. void SListErase(SLTNode** pphead, SLTNode* pos);
    31. //某个位置删除pos -- pos后面位置删除
    32. void SListEraseAfter(SLTNode** pphead, SLTNode* pos);
    33. //销毁
    34. void SListDestory(SLTNode** pphead);

    SList.c

    1. #include "SList.h"
    2. //打印
    3. void SListPrint(SLTNode* phead)
    4. {
    5. SLTNode* cur = phead;
    6. while (cur != NULL)
    7. {
    8. printf("%d->", cur->data);
    9. cur = cur->next; //把下一个节点的地址给cur -- 往下一个节点走
    10. }
    11. printf("NULL\n");
    12. }
    13. //创立一个节点(开辟新空间)
    14. SLTNode* BuySLTNode(SLTDataType x)
    15. {
    16. SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    17. if (newnode == NULL)
    18. {
    19. perror("malloc fail");
    20. exit(-1);
    21. }
    22. newnode->data = x;
    23. newnode->next = NULL;
    24. return newnode;
    25. }
    26. //头插
    27. void SListPushFront(SLTNode** pphead, SLTDataType x)
    28. {
    29. assert(pphead); //pphead 是plist的地址是不可能为空的,所以如果pphead是空说明有问题出现了
    30. SLTNode* newnode = BuySLTNode(x);
    31. newnode->next = *pphead; //这里得使用二级指针,因为改的是phead的指向,而不是改它所指向的值
    32. *pphead = newnode; //更新头
    33. }
    34. //尾插 -- 先要找到尾部
    35. void SListPushBack(SLTNode** phead, SLTDataType x)
    36. {
    37. assert(phead);
    38. SLTNode* newnode = BuySLTNode(x);
    39. //有两种情况:第一种为空、第二种不为空
    40. //第一种情况要改变plist,所以要使用二级指针
    41. //第二种情况只要找到next就行了,一级指针可以实现
    42. //链表为空:插入第一个节点,要改变SListNode*
    43. //链表不为空:尾插,要改变结构体SListNode(里面的next)
    44. //1、空
    45. if (*phead == NULL)
    46. {
    47. *phead = newnode;
    48. }
    49. else//2、不为空
    50. {
    51. SLTNode* tail = *phead;
    52. while (tail->next != NULL) //找到尾部
    53. {
    54. tail = tail->next;
    55. }
    56. tail->next = newnode;
    57. }
    58. }
    59. //头删
    60. void SListPopFront(SLTNode** pphead)
    61. {
    62. assert(pphead); //二级指针一定不为空,因为是一级指针的地址,如果为空就报错了
    63. //温柔的检查
    64. //if (*pphead == NULL)
    65. //{
    66. // return;
    67. //}
    68. //正常删除不会有问题,但是当删多了就会崩溃掉,所以要检查*pphead(就是plist)是不是空,是空就不可以删除了
    69. //暴力的检查 -- 库里面常用
    70. assert(*pphead != NULL);
    71. SLTNode* del = *pphead;
    72. *pphead = (*pphead)->next; //这里要括号,因为优先级是一样的
    73. free(del);
    74. del = NULL;
    75. }
    76. //尾删
    77. //如果直接找尾,free掉空间,就相当与野指针了,上一个节点还是指向这个空间,但是里面的值都变成随机值了 -- 不合适
    78. //正确方式应该找到前一个节点
    79. //两种写法
    80. //第一种:创立两个指针,一前一后走
    81. void SListPopBack(SLTNode** pphead)
    82. {
    83. assert(pphead);
    84. //暴力的检查 -- 同样的也需要检查 -- 不可以删多
    85. assert(*pphead != NULL);
    86. //只有一共节点的时候
    87. if ((*pphead)->next == NULL)
    88. {
    89. free(*pphead);
    90. *pphead = NULL;
    91. }
    92. else//多个节点的时候
    93. {
    94. //第一种:创立两个指针,一前一后走
    95. //assert(pphead);
    96. //SLTNode* prev = *pphead;
    97. //SLTNode* tail = *pphead;
    98. //while (tail->next != NULL)
    99. //{
    100. // prev = tail;
    101. // tail = tail->next;
    102. //}
    103. //prev->next = NULL;
    104. //free(tail); //要改变结构体就要用结构体的指针
    105. //tail = NULL;
    106. //第二种:直接找
    107. SLTNode* tail = *pphead;
    108. while (tail->next->next != NULL)//但是当只有一个节点的时候就有问题了,上面的方法一样,所以要单独拿出来
    109. {
    110. tail = tail->next;
    111. }
    112. free(tail->next);
    113. tail->next = NULL;
    114. }
    115. }
    116. //找到某一个节点
    117. SLTNode* SListFind(SLTNode* phead, SLTDataType x)
    118. {
    119. SLTNode* cur = phead;
    120. while (cur)
    121. {
    122. if (cur->data == x)
    123. {
    124. return cur;
    125. }
    126. //不等于就找下一个节点
    127. cur = cur->next;
    128. }
    129. //没找到就返回NULL
    130. return NULL;
    131. }
    132. //某个位置插入(前插) -- 这里就不再用下标了 -- 默认在pos之前插入
    133. void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
    134. {
    135. assert(pphead);
    136. assert(pos);
    137. //当只有一个节点的时候就会出问题 -- 因为会找不到
    138. //这种情况相当于头插了
    139. if (pos == *pphead)
    140. {
    141. SListPushFront(pphead, x);
    142. }
    143. else
    144. {
    145. SLTNode* prev = *pphead;
    146. while (prev->next != pos)
    147. {
    148. prev = prev->next;
    149. //暴力的检查 -- 同样的也需要检查
    150. assert(prev);
    151. }
    152. SLTNode* newnode = BuySLTNode(x);
    153. prev->next = newnode;
    154. newnode->next = pos;
    155. }
    156. }
    157. //某个位置插入(后插) -- 这个容易实现
    158. void SListInsertAfter(SLTNode* pos, SLTDataType x)
    159. {
    160. assert(pos);
    161. SLTNode* newnode = BuySLTNode(x);
    162. newnode->next = pos->next; //注意顺序,不然就找不到下一个了
    163. pos->next = newnode;
    164. }
    165. //某个位置删除pos -- pos当前位置删除
    166. void SListErase(SLTNode** pphead, SLTNode* pos)
    167. {
    168. assert(pphead);
    169. assert(pos); //理论上pos不应该为空
    170. if (*pphead == pos) //只有一个节点,相当于头删
    171. {
    172. SListPopFront(pphead); //直接调用头删
    173. }
    174. else
    175. {
    176. SLTNode* prev = *pphead;
    177. while (prev->next != pos )
    178. {
    179. prev = prev->next;
    180. //检查pos是不是链表中的节点
    181. assert(prev);
    182. }
    183. prev->next = pos->next;
    184. free(pos);
    185. //pos = NULL; -- 这里置空并没有用,因为出去的时候就会销毁掉,要使用二级指针,不过不要这样做,要改变很多,而且没必要
    186. //这种就和free一样 -- free并不会置空指针,需要使用者置空
    187. }
    188. }
    189. //某个位置删除pos -- pos后面位置删除
    190. void SListEraseAfter(SLTNode** pphead, SLTNode* pos)
    191. {
    192. assert(pos);
    193. if (pos->next == NULL) //如果没有就直接返回就可以了
    194. {
    195. return;
    196. }
    197. else
    198. {
    199. SLTNode* next = pos->next;
    200. pos->next = next->next;
    201. free(next);
    202. }
    203. }
    204. //销毁
    205. //销毁的时候先要保存下一个节点的地址,不然free掉节点就找不到下一个节点了
    206. void SListDestory(SLTNode** pphead) //里面致空不影响外面的plist,应该使用二级指针
    207. {
    208. assert(pphead);
    209. SLTNode* cur = *pphead;
    210. while (cur)
    211. {
    212. SLTNode* next = cur->next;
    213. free(cur);
    214. cur = next;
    215. }
    216. *pphead = NULL; //置空
    217. }

    Test.c

    1. #include "SList.h"
    2. //头插、头删测试
    3. void TestSList1()
    4. {
    5. SLTNode* plist = NULL; //改什么值就要传什么的地址(局部是不行的)
    6. SListPushFront(&plist, 1); //得传地址,因为要改结构体指针,所以要传结构体指针的地址
    7. SListPushFront(&plist, 2);
    8. SListPushFront(&plist, 3);
    9. SListPushFront(&plist, 4);
    10. SListPrint(plist);
    11. SListPopFront(&plist); //删四次
    12. SListPrint(plist);
    13. SListPopFront(&plist);
    14. SListPrint(plist);
    15. SListPopFront(&plist);
    16. SListPrint(plist);
    17. SListPopFront(&plist);
    18. SListPrint(plist);
    19. //销毁
    20. SListDestory(&plist);
    21. }
    22. //尾插、尾删测试
    23. void TestSList2()
    24. {
    25. SLTNode* plist = NULL;
    26. SListPushBack(&plist, 1);
    27. SListPushBack(&plist, 2);
    28. SListPushBack(&plist, 3);
    29. SListPushBack(&plist, 4);
    30. SListPrint(plist);
    31. SListPopBack(&plist); //删四次
    32. SListPrint(plist);
    33. SListPopBack(&plist);
    34. SListPrint(plist);
    35. SListPopBack(&plist);
    36. SListPrint(plist);
    37. SListPopBack(&plist);
    38. SListPrint(plist);
    39. //SListPopBack(&plist); //多删一次
    40. //SListPrint(plist);
    41. //销毁
    42. SListDestory(&plist);
    43. }
    44. //销毁、查找测试
    45. TestSList3()
    46. {
    47. SLTNode* plist = NULL;
    48. SListPushBack(&plist, 1);
    49. SListPushBack(&plist, 2);
    50. SListPushBack(&plist, 3);
    51. SListPushBack(&plist, 4);
    52. SListPrint(plist);
    53. SLTNode* pos = SListFind(plist, 3);
    54. if (pos)
    55. {
    56. printf("找到了\n");
    57. //查找到就可以修改了,因为返回的是指针,可以直接修改
    58. pos->data *= 10;
    59. }
    60. else
    61. {
    62. printf("没找到\n");
    63. }
    64. SListPrint(plist);
    65. pos = SListFind(plist, 1); //在1前面插就相当与头插了
    66. if (pos)
    67. {
    68. SListInsert(&plist, pos, 30);
    69. }
    70. SListPrint(plist);
    71. SListDestory(&plist);
    72. }
    73. //某个位置的删除测试
    74. void TestSList4()
    75. {
    76. SLTNode* plist = NULL;
    77. SListPushBack(&plist, 1);
    78. SListPushBack(&plist, 2);
    79. SListPushBack(&plist, 3);
    80. SListPushBack(&plist, 4);
    81. SListPrint(plist);
    82. //删除某一个
    83. SLTNode* pos = SListFind(plist, 4);
    84. if (pos)
    85. {
    86. SListErase(&plist, pos);
    87. //这里要注意pos的野指针问题 -- pos节点的地址是不变的,但是里面的值变成随机值了,所以要置空
    88. pos = NULL;
    89. }
    90. SListPrint(plist);
    91. //删头
    92. pos = SListFind(plist, 1);
    93. if (pos)
    94. {
    95. SListErase(&plist, pos);
    96. }
    97. SListPrint(plist);
    98. }
    99. //单链表并不会很好的解决顺序表的所以问题,单链表适合头部的操作,删除、插入之类的 -- 时间复杂度达到了O(1)
    100. //如果要任意高效插入删除需要 -- 用到双向链表
    101. //单链表开始是不需要初始化的 -- 开始置空就行了
    102. int main()
    103. {
    104. //TestSList1();
    105. //TestSList2();
    106. //TestSList3();
    107. TestSList4();
    108. return 0;
    109. }

            数据结构是不需要写什么菜单之类的,不过这个单链表还是有些问题无法得到很好的解决,于是就又有了进一步的链表,双向链表

    结束语 

    枝上柳绵吹又少,天涯何处无芳草。
                                            宋·苏轼 《蝶恋花·春景》

  • 相关阅读:
    Axure教程——分级下拉选择器
    Java—Map
    ORACLE数据恢复(误操作delete或update如何恢复?)
    FFmpeg入门详解之6:VLC播放器简介
    SpringBoot+Vue项目网上家电商城
    Ubuntu18 无法加载登录界面【100%解决】
    px转rem插件postcss-plugin-px2rem使用方法(浏览器缩放页面自适应)
    c++异常处理
    Spring Boot 3 新特性及快速使用示例
    选低代码开发的OA系统,对低效办公说“漏”
  • 原文地址:https://blog.csdn.net/weixin_67595436/article/details/126076383