• 【数据结构七夕专属版】单链表及单链表的实现【附源码和源码讲解】


    本篇是博主在学习数据结构时的心得,希望能够帮助到大家,也许有些许遗漏,但博主已经尽了最大努力打破信息差,如果有遗漏还请见谅,嘻嘻,前路漫漫,我们一起前进!!!!

    今天是七夕!!!虽然博主没有女朋友,但是在此我也祝各位有情人终成眷属。爱永无止境。 

    希望你们都能遇到自己的米子哈和大kin库!!!!!!!!!!!!!!!!!!!!!!! 

     


    目录

     1.单链表的简介

    1.1结点

    结点申请规则 

    单链表和顺序表的对比 

    2、单链表的实现及其对应源码:

    2.1单链表的创建:

     2.2.链表的申请结点

     2.3.单链表的打印

    2.4.链表的尾插

    2.5. 链表的头插

    2.6.链表的尾删

    2.7.链表的头删

    2. 8.链表的查找

    2.9. 链表的指定位置前的结点插入

    2.10. 在指定位置后插入结点

    2.11.删除pos结点

    2.12.销毁链表

     1.单链表的简介

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

    1.1结点

    链表中每个结点都是独⽴申请的(即需要插⼊数据时才去申请⼀块结点的空间),我们需要通过指针变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点。

    结点申请规则 

    1. 链式机构在逻辑上是连续的,在物理结构上不⼀定连续
    2. 结点⼀般是从堆上申请的
    3. 从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续  

    单链表申请空间:

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

    在我们想要存储一个整形数据时,实际先向内存申请一块空间,这个空间不仅要存放当前结点的数据,还存放着下一个结点的地址。(最后的结点存储的地址为NULL) 


    单链表和顺序表的对比 

    1. 存储结构:
      单链表的存储结构是非连续,非顺序的依靠地址的来寻找链表中的下一位数据。
      顺序表在物理存储结构上是连续的,内存读取依次访问数据元素。
    2. 逻辑顺序:
      单链表的逻辑顺序是依靠结点相连接的,
      顺序标的逻辑顺序和物理顺序一样是紧挨着的。
       
    3. 代码实现:
      单链表的实现需要依靠指针
      顺序表可以不用指针,用访问符就能访问到数据
       
    4. 数据的存储:
      在顺序表中,只需要开辟一系列的相邻的一块空间进行数据的存储。
      在单链表中,每一个数据的位置都要申请,申请下来的空间叫做“结点”。

    图解


    2、单链表的实现及其对应源码:

    单链表包括创建、申请结点、打印、头插、尾插、头删、尾删、链表查找、指定前插入、指定后插入、删除pos结点、删除pos后结点、销毁链表

    单链表的实现 :

    2.1单链表的创建:

    1. typedef int SLDataType
    2. typedef struct SListNode
    3. {
    4. SLDataType Data;
    5. struct SListNode*next;
    6. }sL;

    以上代码是对链表结点结构的声明,该链表结点包括一个类型为SLDataType的整形数据一个地址。


     

     2.2.链表的申请结点

    1. SL*SLTButNode(SLTDataType x)
    2. {
    3. SL*newnode=(SL*)malloc(sizeof(SL));
    4. if(newnode==NULL)
    5. {
    6. perror("malloc fail");
    7. exit(1);
    8. }
    9. newnode->data=x;
    10. newnode->next=NULLl;
    11. }

    我们用图表示一下:


     2.3.单链表的打印

    1. void SLprint(SL*phead)
    2. {
    3. SL*pucr=phead;
    4. while(pcur)
    5. {
    6. printf("%d",pucr->next);
    7. }
    8. printf("NULL/n");
    9. }

     这下我们用图解:

     这里有几个注意的点:

    1.*phead一定是该链表中的第一个结点。

    2.对pcur解引用拿到下一个结点的指针才能进入下一次循环,不然pcur走不动,打印陷入死循环。

    2.4.链表的尾插

    1. void SLTPushBack(SL**pphead,SLTDataType x)
    2. {
    3. assert(pphead);
    4. SL*newnode=SLButNode(x);
    5. if(*pphead==NULL)
    6. {
    7. *pphead=newnode;
    8. }
    9. else
    10. {
    11. SL*pcur=*pphead;
    12. while(pcur->next)
    13. {
    14. pucr=pucr->next;
    15. }
    16. pcur->next=newnode;
    17. }

    如果*pphead是NULL时说明该链表的头结点还没有建立,所以如果从对一个没有头节点的链表进行尾插,头节点就是尾插的目标。

    当*pphead不是NULL的时代表要在链表结点之后插入,为了更直观的感受,我们用图解的方式来解释:


    2.5. 链表的头插

    1. void SLTPushFront(SL**pphead,SLDataType x)
    2. {
    3. assert(pphead&&*pphead);
    4. SL*newnode=SLBuynode(x);
    5. newnode->next=*pphead;
    6. *pphead=newnode;
    7. }

    头插相较于尾插简单的多:

    只要将先将头结点赋值给新的结点的存储地址,然后在再将新节点newnode当作新的头结点*pphead即可; 


    2.6.链表的尾删

    1. void SLTpopBack(SL**pphead)
    2. {
    3. //链表为空:不可以删除
    4. assert(pphead && *pphead);
    5. //处理只有一个结点的情况:要删除的就是头结点
    6. if((*pphead)->next=NULL)
    7. {
    8. free(*pphead);
    9. *pphead=NULL;
    10. }
    11. else
    12. {
    13. SL* ptail = *pphead;
    14. SL* prev = NULL;
    15. while (ptail->next)
    16. {
    17. prev = ptail;
    18. ptail = ptail->next;
    19. }
    20. prev->next = NULL;
    21. free(ptail);
    22. ptail = NULL;
    23. }

    这里我们还是用图解的方式来讲解:

    尾删详解:这里用prev作为ptail的前一个指针,当ptail找到最后一个结点的时候,prev就是尾删的尾结点,所以先将prev的存储的下一个结点地址(prev->next)置为NULL,再将ptail所指的空间释放,最后ptail指向NULL,这就是尾删的过程

     


    2.7.链表的头删

    1. void SLTPopFront(SLTNode** pphead)
    2. {
    3. assert(pphead && *pphead);
    4. SL* next = (*pphead)->next;
    5. //*pphead --> next
    6. free(*pphead);
    7. *pphead = next;
    8. }

    这里头删就更加简单了,头结点就是要删除的结点,直接将头结点的指向的下一个结点的地址存储到next中,在进行释放头结点空间,最后在对新结点进行操作


    2. 8.链表的查找

    1. SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
    2. {
    3. assert(phead);
    4. SLTNode* pcur = phead;
    5. while (pcur)
    6. {
    7. if (pcur->data == x)
    8. {
    9. return pcur;
    10. }
    11. pcur = pcur->next;
    12. }
    13. //没有找到
    14. return NULL;
    15. }

    查找详解:

    1. 我们用pcur指针对单链表进行遍历,遍历过程中如果找到是否有与x匹配的数据,说明找到了,返回与x相等数据的pcur指针。
    2. 用pcur遍历完之后,如果没有找到与之匹配的数据,说明没有找到,返回NULL 。


    2.9. 链表的指定位置前的结点插入

    1. void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
    2. {
    3. assert(pphead);
    4. assert(pos);
    5. if (pos == *pphead)
    6. {
    7. SLTPushFront(pphead, x);
    8. }
    9. else
    10. {
    11. SLTNode* newnode = SLTBuyNode(x);
    12. //找prev :pos的前一个结点
    13. SLTNode* prev = *pphead;
    14. while (prev->next != pos)
    15. {
    16. prev = prev->next;
    17. }
    18. //prev newnode --> pos
    19. newnode->next = pos;
    20. prev->next = newnode;
    21. }
    22. }

    插入(位置前)详解:

    1. 如果pos的位置在头结点,我们直接在头结点头插即可。
    2. 如果pos不在头结点,我们先创建一个新的结点,用prev指针找到pos的前一个结点的地址,pos的本质是一个地址,我们用将新节点存储的地址给pos,newnode的地址给到prev指针所指向结点的存储地址。这样我们就将新节点插入到了pos结点的前面 


    2.10. 在指定位置后插入结点

    1. void SLTInsertAfter(SLTNode* pos, SLTDataType x)
    2. {
    3. assert(pos);
    4. SLTNode* newnode = SLTBuyNode(x);
    5. //pos newnode --> pos->next
    6. newnode->next = pos->next;
    7. pos->next = newnode;
    8. }

    插入(位置后)详解:

    在指定位置后插入数据不需要用循环找pos后的位置了,因为pos所在的结点存储的地址就是下一个结点的地址,所以将pos存储的下一个结点的地址赋值给newnode的存储地址之后再将pos的存储地址给newnode的地址,这样就完成了位置后插入。


    2.11.删除pos结点

    1. void SLTErase(SLTNode** pphead, SLTNode* pos)
    2. {
    3. assert(pphead && *pphead);
    4. assert(pos);
    5. //头删
    6. if (pos == *pphead)
    7. {
    8. SLTPopFront(pphead);
    9. }
    10. else
    11. {
    12. SLTNode* prev = *pphead;
    13. while (prev->next != pos)
    14. {
    15. prev = prev->next;
    16. }
    17. //prev pos pos->next
    18. prev->next = pos->next;
    19. free(pos);
    20. pos = NULL;
    21. }
    22. }
    23. //删除pos之后的结点
    24. void SLTEraseAfter(SLTNode* pos)
    25. {
    26. assert(pos && pos->next);
    27. //pos pos->next pos->next->next
    28. SLTNode* del = pos->next;
    29. pos->next = pos->next->next;
    30. free(del);
    31. del = NULL;
    32. }

    删除pos结点详解: 

    删除pos结点:用prev指针找到pos前一个结点的位置,用prev所在的结点的存储地址改为pos->next(pos后一个结点)之后释放pos结点的空间,最后将pos指针置为NULL

    删除pos之后的结点:用del代表pos的下一个结点,将pos的下下个结点的地址给pos的存储地址,释放del,将del指针置为NULL;


    2.12.销毁链表

    1. void SListDestroy(SLTNode** pphead)
    2. {
    3. assert(pphead && *pphead);
    4. SLTNode* pcur = *pphead;
    5. while (pcur)
    6. {
    7. SLTNode* next = pcur->next;
    8. free(pcur);
    9. pcur = next;
    10. }
    11. *pphead = NULL;
    12. }

    超详细源码:

    cpp文件:

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include"Slist.h"
    3. void SLprint(SLTNode*phead)
    4. {
    5. SLTNode* pcur = phead;
    6. while (pcur)
    7. {
    8. printf("%d", pcur->data);
    9. }
    10. printf("NULL/n");
    11. }
    12. SLTNode* SLTBuyNode(SLTDataType x)
    13. {
    14. SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    15. if(newnode==NULL)
    16. {
    17. perror("malloc fail");
    18. exit(1);
    19. }
    20. newnode->data = x;
    21. newnode->next = NULL;
    22. }
    23. void SLTPushBack(SLTNode** pphead, SLTDataType x)
    24. {
    25. assert(pphead);
    26. SLTNode* newnode = SLTBuyNode(x);
    27. if (pphead == NULL)
    28. {
    29. *pphead = newnode;
    30. }
    31. else
    32. {
    33. //找尾结点
    34. SLTNode* pcur = *pphead;
    35. while (pcur->next)
    36. {
    37. pcur = pcur->next;
    38. }
    39. //pcur newnode
    40. pcur->next = newnode;
    41. n }
    42. }
    43. void SLTPushFront(SLTNode** pphead, SLTDataType x)
    44. {
    45. assert(pphead);
    46. SLTNode* newnode = SLTBuyNode(x);
    47. //newnode *pphead
    48. newnode->next = *pphead;
    49. *pphead = newnode;
    50. }
    51. void SLTPopBack(SLTNode** pphead)
    52. {
    53. //链表为空:不可以删除
    54. assert(pphead && *pphead);
    55. //处理只有一个结点的情况:要删除的就是头结点
    56. if ((*pphead)->next == NULL)
    57. {
    58. free(*pphead);
    59. *pphead = NULL;
    60. }
    61. else
    62. {
    63. //找 prev ptail
    64. SLTNode* ptail = *pphead;
    65. SLTNode* prev = NULL;
    66. while (ptail->next)
    67. {
    68. prev = ptail;
    69. ptail = ptail->next;
    70. }
    71. prev->next = NULL;
    72. free(ptail);
    73. ptail = NULL;
    74. }
    75. }
    76. void SLTPopFront(SLTNode** pphead)
    77. {
    78. assert(pphead && *pphead);
    79. SLTNode* next = (*pphead)->next;
    80. //*pphead --> next
    81. free(*pphead);
    82. *pphead = next;
    83. }
    84. SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
    85. {
    86. assert(phead);
    87. SLTNode* pcur = phead;
    88. while (pcur)
    89. {
    90. if (pcur->data == x)
    91. {
    92. return pcur;
    93. }
    94. pcur = pcur->next;
    95. }
    96. //没有找到
    97. return NULL;
    98. }
    99. void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
    100. {
    101. assert(pphead);
    102. assert(pos);
    103. if (pos == *pphead)
    104. {
    105. SLTPushFront(pphead, x);
    106. }
    107. else
    108. {
    109. SLTNode* newnode = SLTBuyNode(x);
    110. //找prev :pos的前一个结点
    111. SLTNode* prev = *pphead;
    112. while (prev->next != pos)
    113. {
    114. prev = prev->next;
    115. }
    116. //prev newnode --> pos
    117. newnode->next = pos;
    118. prev->next = newnode;
    119. }
    120. }
    121. //在指定位置之后插⼊数据
    122. void SLTInsertAfter(SLTNode* pos, SLTDataType x)
    123. {
    124. assert(pos);
    125. SLTNode* newnode = SLTBuyNode(x);
    126. //pos newnode --> pos->next
    127. newnode->next = pos->next;
    128. pos->next = newnode;
    129. }
    130. //删除pos结点
    131. void SLTErase(SLTNode** pphead, SLTNode* pos)
    132. {
    133. assert(pphead && *pphead);
    134. assert(pos);
    135. //头删
    136. if (pos == *pphead)
    137. {
    138. SLTPopFront(pphead);
    139. }
    140. else
    141. {
    142. SLTNode* prev = *pphead;
    143. while (prev->next != pos)
    144. {
    145. prev = prev->next;
    146. }
    147. //prev pos pos->next
    148. prev->next = pos->next;
    149. free(pos);
    150. pos = NULL;
    151. }
    152. }
    153. //删除pos之后的结点
    154. void SLTEraseAfter(SLTNode* pos)
    155. {
    156. assert(pos && pos->next);
    157. //pos pos->next pos->next->next
    158. SLTNode* del = pos->next;
    159. pos->next = pos->next->next;
    160. free(del);
    161. del = NULL;
    162. }
    163. //销毁链表
    164. void SListDestroy(SLTNode** pphead)
    165. {
    166. assert(pphead && *pphead);
    167. SLTNode* pcur = *pphead;
    168. while (pcur)
    169. {
    170. SLTNode* next = pcur->next;
    171. free(pcur);
    172. pcur = next;
    173. }
    174. *pphead = NULL;
    175. }

    .h文件

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. //定义链表(结点)的结构
    6. typedef int SLTDataType;
    7. typedef struct SListNode {
    8. SLTDataType data;
    9. struct SListNode* next;
    10. }SLTNode;
    11. void SLTPrint(SLTNode* phead);
    12. //插入
    13. void SLTPushBack(SLTNode** pphead, SLTDataType x);//尾插
    14. void SLTPushFront(SLTNode** pphead, SLTDataType x);//头插
    15. //删除
    16. void SLTPopBack(SLTNode** pphead);
    17. void SLTPopFront(SLTNode** pphead);
    18. //查找
    19. SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
    20. //在指定位置之前插⼊数据
    21. void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
    22. //在指定位置之后插⼊数据
    23. void SLTInsertAfter(SLTNode* pos, SLTDataType x);
    24. //删除pos结点
    25. void SLTErase(SLTNode** pphead, SLTNode* pos);
    26. //删除pos之后的结点
    27. void SLTEraseAfter(SLTNode* pos);
    28. //销毁链表
    29. void SListDestroy(SLTNode** pphead);


    结束语: 

    本篇单链表的章节到这里就结束了..........

    感谢各位能观看到这里,感谢大家支持博主,很开心能和大家一起共同进步,一起学习!!!!!我希望能让更多的人看到这篇文章,希望大家能够多点赞,多交流。可以点波收藏,忘记的时候可以观看,在此wheel down先谢过大家!!!!!
     

    时间漫长,我们一起度过,前路未知,我们一起并肩。 

  • 相关阅读:
    Python —— hou.Node class
    1.MySQL数据库介绍和基础操作
    vue3 - 开发和生产环境通过Mock模拟真实接口请求
    Navicat将视图结构导出为可运行SQL文件
    使用POI实现操作Excel文件。
    [论文精读|博士论文]面向文本数据的关系抽取关键技术研究
    Android 和 iOS APP 测试的那些区别
    OpenWrt之跳过tools编译
    Android端ReactNative环境搭建——下
    前端实战|React18极客园——布局模块(useRoutes路由配置、处理Token失效、退出登录)
  • 原文地址:https://blog.csdn.net/wheeldown/article/details/141039231