当前位置:   article > 正文

【数据结构】--- 单链表实现

【数据结构】--- 单链表实现

                                                        欢迎光顾我的homepage

前言        

        链表和顺序表都是线性表的一种,但是顺序表在物理结构和逻辑结构上都是连续的,但链表在逻辑结构上是连续的,而在物理结构上不一定连续;来看以下图片来认识链表与顺序表的差别

这里以动态顺序表为例,和链表中的单链表对比一下

动态顺序表

单链表

        这里就可以很清晰的看到顺序表的底层其实就是一个数组,数据的是连续存储的(顺序表物理结构连续);而链表它每一个数据都不是连续的(链表物理结构上不一定连续)。

链表节点

        通过观察上图,我们会发现链表每一个节点都存放在数据和下一个节点的地址。

        那么来想一下,为了链表每一个节点都统一起来,都存储有效数据和下一个节点的地址,我们就需要创建应该结构体,来存储有效数据和下一个节点的指针;
注:这里只是单链表

  1. typedef int SLType;
  2. typedef struct SLTNode
  3. {
  4. SLType data;
  5. struct SLTNode* next;
  6. }SLT;

创建好链表节点,接下来就来实习单链表存储数据的这些功能。

单链表实现

先来看一下单链表都实现都哪些功能

  1. //输出链表
  2. void SLTPrint(SLT* phead);
  3. //创建节点
  4. SLT* SLTCreat(SLType x);
  5. //单链表头插
  6. void SLTPushFront(SLT** pphead, SLType x);
  7. //单链表尾插
  8. void SLTPushBack(SLT** pphead, SLType x);
  9. //单链表头删
  10. void SLTPopFront(SLT** pphead);
  11. //单链表尾删
  12. void SLTPopBack(SLT** pphead);
  13. //查找数据
  14. SLT* SLTFind(SLT* phead, SLType x);
  15. //指定位置之前插入
  16. void SLTInsert(SLT** pphead, SLT* pos, SLType x);
  17. //指定位置之后插入
  18. void SLTInsertAfter(SLT* pos, SLType x);
  19. //删除指定节点
  20. void SLTErase(SLT** pphead, SLT* pos);
  21. //删除指定位置后一个节点
  22. void SLTEraseAfter(SLT* pos);

创建节点

        这里创建节点,还是使用动态内存来创建,创建完成后,将数据存储进去,并把新节点的下一个节点置为NULL

代码如下:

  1. //创建节点
  2. SLT* SLTCreat(SLType x)
  3. {
  4. SLT* newnode = (SLT*)malloc(sizeof(SLT));
  5. assert(newnode);
  6. newnode->data = x;
  7. newnode->next = NULL;
  8. return newnode;
  9. }

        测试一下代码是否正确

可以看到代码没有问题。

输出链表

        由于这里实现单链表,存储的是整型数据,就以整型的方式输出,若存储其他类型的数据,就以存储类型的方式输出。

输出链表,首先就要遍历链表,因为链表最后一个节点里存储的下一个节点的地址为空(即最后一个节点  ->next 为空)所以,遍历链表结束的条件就是ptail ==NULL,没输出一个就让ptail指向下一个节点,直到为空,遍历结束。

        来写代码实现:

  1. //输出链表
  2. void SLTPrint(SLT* phead)
  3. {
  4. SLT* ptail = phead;
  5. while (ptail!= NULL)//也可以直接写成 ptail
  6. {
  7. printf("%d -> ", ptail->data);
  8. ptail = ptail->next;
  9. }
  10. printf("NULL\n");
  11. }

这里先创建两个节点并存储数据输出看一下结果

  1. int main()
  2. {
  3. SLT* plist = SLTCreat(1);
  4. plist->next = SLTCreat(2);
  5. SLTPrint(plist);
  6. return 0;
  7. }

        这里也成功输出插入的两个数据。(最后一个节点的next指向空)

单链表头插

        在链表头部插入数据,不用像顺序表那样去移动所以的有效数据,链表只需要改变一个指针的指向即可

假设现在链表中已经存在两个数据,再进行头插,这时就只需要改变plist的指向即可,当然新节点的next指针也要指向下一个节点(plist指向的节点)。

代码如下:

  1. //单链表头插
  2. void SLTPushFront(SLT** pphead, SLType x)
  3. {
  4. assert(pphead);
  5. SLT* newnode = SLTCreat(x);
  6. newnode->next = *pphead;
  7. *pphead = newnode;
  8. }

还有一种情况,如果现在链表中没有数据,再进行头插,这里代码也能实现链表没有数据时的头插

单链表尾插

        链表的尾插,因为指针传的是指向头节点的指针的地址,所以,我们需要先遍历链表,找到链表的尾部,再修改尾节点的next指针指向。

假设现在链表中已经存在两个数据,再进行尾插,此时我们只需要找到链表的尾部,修改尾节点next指针指向即可,代码如下

  1. //单链表尾插
  2. void SLTPushBack(SLT** pphead, SLType x)
  3. {
  4. assert(pphead);
  5. SLT* newnode = SLTCreat(x);
  6. SLT* ptail = *pphead;
  7. //遍历链表
  8. while (ptail->next)
  9. {
  10. ptail = ptail->next;
  11. }
  12. ptail->next = newnode;
  13. }

        考虑了这种情况,再来看以下链表为空的情况,如果链表为空,这里ptail->next就对空指针进行解引用操作了;所以,我们需要判断链表是否为空?如果为空,插入的新节点就是头节点,直接让plist(即*pphead)指向新节点即可;如果不为空就正常进行尾插。

最终代码如下:

  1. //单链表尾插
  2. void SLTPushBack(SLT** pphead, SLType x)
  3. {
  4. assert(pphead);
  5. SLT* newnode = SLTCreat(x);
  6. if (*pphead == NULL) //判断链表是否为空
  7. {
  8. *pphead = newnode;
  9. }
  10. else {
  11. SLT* ptail = *pphead;
  12. //遍历链表
  13. while (ptail->next)
  14. {
  15. ptail = ptail->next;
  16. }
  17. ptail->next = newnode;
  18. }
  19. }

这里代码可以正常进行尾插。

单链表头删

        链表头删,首先我们要判断链表是否为空,如果为空(空链表没有数据该如何删除呢?

链表头删,我们需要修改plist(*pphead)指向链表的下一个节点即头节点的next指针。

        此外:因为我们的节点都是动态申请的内存,所以在删除时,需要把它释放掉。

思路很简单,写一下代码:
 

  1. //单链表头删
  2. void SLTPopFront(SLT** pphead)
  3. {
  4. assert(pphead && *pphead);
  5. SLT* del = (*pphead);
  6. *pphead = (*pphead)->next;
  7. free(del);
  8. del = NULL;
  9. }

再来看一个如果链表为空,又是啥结果呢?

现在链表已经为空,在执行一次头删代码

这里assert断言报错。

单链表尾删

        首先尾删与头删一样,链表不能为空。

        尾删与尾插也有共同之处,就是遍历链表,找到链表的尾节点。找到尾节点,删除尾节点;然后修改尾节点上一个节点的next指针为NULL;所以在遍历链表时就要多记录一个节点。

  1. //单链表尾删
  2. void SLTPopBack(SLT** pphead)
  3. {
  4. assert(pphead && *pphead);
  5. SLT* ptail = *pphead;
  6. SLT* pcur = *pphead;
  7. //遍历链表
  8. while (ptail->next)
  9. {
  10. pcur = ptail;
  11. ptail = ptail->next;
  12. }
  13. pcur->next = NULL;
  14. free(ptail);
  15. ptail = NULL;
  16. }

        在测试的时候我们会发现一个问题,如果链表只有一个节点,删除之后我们的plist指针并没有置为空,而我们的空间已经释放掉了,这是很危险的;所以这里我们先判断一下链表是否只有一个节点,如果是,我们释放完空间以后,将(*pphead)置为空,以免出现野指针的情况。

完善后代码:

  1. //单链表尾删
  2. void SLTPopBack(SLT** pphead)
  3. {
  4. assert(pphead && *pphead);
  5. if ((*pphead)->next== NULL)
  6. {
  7. free(*pphead);
  8. *pphead = NULL;
  9. }
  10. else {
  11. SLT* ptail = *pphead;
  12. SLT* pcur = *pphead;
  13. //遍历链表
  14. while (ptail->next)
  15. {
  16. pcur = ptail;
  17. ptail = ptail->next;
  18. }
  19. free(ptail);
  20. ptail = NULL;
  21. pcur->next = NULL;
  22. }
  23. }

测试没有问题,代码能够完成尾删这个功能。

查找数据

        查找数据,遍历链表查找数据,如果找到就返回数据所在节点的地址,如果没有找到就返回NULL;

  1. //查找数据
  2. SLT* SLTFind(SLT* phead, SLType x)
  3. {
  4. SLT* ptail = phead;
  5. while (ptail)
  6. {
  7. if (ptail->data == x)
  8. {
  9. return ptail;
  10. }
  11. ptail = ptail->next;
  12. }
  13. return NULL;
  14. }

这里测试以下:

数据存在时

数据不存在时

指定节点之前插入

        在链表指定节点之前插入数据,我们还需要知道指定节点的前一个节点,所以仍然需要遍历链表,寻找指定节点pos前一个节点。

  1. //指定位置之前插入
  2. void SLTInsert(SLT** pphead, SLT* pos, SLType x)
  3. {
  4. assert(pphead && *pphead);
  5. SLT* ptail = *pphead;
  6. if (ptail == pos)//头插
  7. {
  8. SLTPushFront(pphead, x);
  9. }
  10. else
  11. {
  12. SLT* newnode = SLTCreat(x);
  13. while (ptail->next != pos)//找到pos位置
  14. {
  15. ptail = ptail->next;
  16. }
  17. newnode->next = pos;
  18. ptail->next = newnode;
  19. }
  20. }

当然,这里如果故意传NULL给pos,(这就没啥意义了)这里也可以写以下assert断言pos。

指定节点之后插入

        在指定节点之后插入数据,就不需要再进行遍历链表,这里直接插入即可。

  1. //指定位置之后插入
  2. void SLTInsertAfter(SLT* pos, SLType x)
  3. {
  4. assert(pos);
  5. SLT* newnode = SLTCreat(x);
  6. newnode->next = pos->next;
  7. pos->next = newnode;
  8. }

删除指定节点

        删除链表中的指定节点,我们需要这个节点的上一个节点,所以又需要遍历链表,找到pos上一个节点,修改pos->next指针指向。

代码如下:

  1. //删除指定节点
  2. void SLTErase(SLT** pphead, SLT* pos)
  3. {
  4. //找到pos上一个节点
  5. SLT* ptail = *pphead;
  6. while (ptail->next != pos)
  7. {
  8. ptail = ptail->next;
  9. }
  10. SLT* p = pos->next;
  11. free(pos);
  12. pos = NULL;
  13. ptail->next = p;
  14. }

删除指定节点后一个节点

        删除链表指定节点后一个节点,因为pos就是删除节点的上一个节点,所以不需要遍历链表,直接删除即可。

  1. //删除指定位置后一个节点
  2. void SLTEraseAfter(SLT* pos)
  3. {
  4. assert(pos->next);
  5. SLT* del = pos->next;
  6. pos->next = pos->next->next;
  7. free(del);
  8. del = NULL;
  9. }

这里如果传过来的就是链表的尾节点,那没办法删除后一个节点,所以断言pos->next;

代码总览

  1. #include"SList.h"
  2. //创建节点
  3. SLT* SLTCreat(SLType x)
  4. {
  5. SLT* newnode = (SLT*)malloc(sizeof(SLT));
  6. assert(newnode);
  7. newnode->data = x;
  8. newnode->next = NULL;
  9. return newnode;
  10. }
  11. //输出链表
  12. void SLTPrint(SLT* phead)
  13. {
  14. SLT* ptail = phead;
  15. while (ptail != NULL)//也可以直接写成 ptail
  16. {
  17. printf("%d -> ", ptail->data);
  18. ptail = ptail->next;
  19. }
  20. printf("NULL\n");
  21. }
  22. //单链表头插
  23. void SLTPushFront(SLT** pphead, SLType x)
  24. {
  25. assert(pphead);
  26. SLT* newnode = SLTCreat(x);
  27. newnode->next = *pphead;
  28. *pphead = newnode;
  29. }
  30. //单链表尾插
  31. void SLTPushBack(SLT** pphead, SLType x)
  32. {
  33. assert(pphead);
  34. SLT* newnode = SLTCreat(x);
  35. if (*pphead == NULL) //判断链表是否为空
  36. {
  37. *pphead = newnode;
  38. }
  39. else {
  40. SLT* ptail = *pphead;
  41. //遍历链表
  42. while (ptail->next)
  43. {
  44. ptail = ptail->next;
  45. }
  46. ptail->next = newnode;
  47. }
  48. }
  49. //单链表头删
  50. void SLTPopFront(SLT** pphead)
  51. {
  52. assert(pphead && *pphead);
  53. SLT* del = (*pphead);
  54. *pphead = (*pphead)->next;
  55. free(del);
  56. del = NULL;
  57. }
  58. //单链表尾删
  59. void SLTPopBack(SLT** pphead)
  60. {
  61. assert(pphead && *pphead);
  62. if ((*pphead)->next== NULL)
  63. {
  64. free(*pphead);
  65. *pphead = NULL;
  66. }
  67. else {
  68. SLT* ptail = *pphead;
  69. SLT* pcur = *pphead;
  70. //遍历链表
  71. while (ptail->next)
  72. {
  73. pcur = ptail;
  74. ptail = ptail->next;
  75. }
  76. free(ptail);
  77. ptail = NULL;
  78. pcur->next = NULL;
  79. }
  80. }
  81. //查找数据
  82. SLT* SLTFind(SLT* phead, SLType x)
  83. {
  84. SLT* ptail = phead;
  85. while (ptail)
  86. {
  87. if (ptail->data == x)
  88. {
  89. return ptail;
  90. }
  91. ptail = ptail->next;
  92. }
  93. return NULL;
  94. }
  95. //指定位置之前插入
  96. void SLTInsert(SLT** pphead, SLT* pos, SLType x)
  97. {
  98. assert(pphead && *pphead);
  99. SLT* ptail = *pphead;
  100. if (ptail == pos)//头插
  101. {
  102. SLTPushFront(pphead, x);
  103. }
  104. else
  105. {
  106. SLT* newnode = SLTCreat(x);
  107. while (ptail->next != pos)//找到pos位置
  108. {
  109. ptail = ptail->next;
  110. }
  111. newnode->next = pos;
  112. ptail->next = newnode;
  113. }
  114. }
  115. //指定位置之后插入
  116. void SLTInsertAfter(SLT* pos, SLType x)
  117. {
  118. assert(pos);
  119. SLT* newnode = SLTCreat(x);
  120. newnode->next = pos->next;
  121. pos->next = newnode;
  122. }
  123. //删除指定节点
  124. void SLTErase(SLT** pphead, SLT* pos)
  125. {
  126. //找到pos上一个节点
  127. SLT* ptail = *pphead;
  128. while (ptail->next != pos)
  129. {
  130. ptail = ptail->next;
  131. }
  132. SLT* p = pos->next;
  133. free(pos);
  134. pos = NULL;
  135. ptail->next = p;
  136. }
  137. //删除指定位置后一个节点
  138. void SLTEraseAfter(SLT* pos)
  139. {
  140. assert(pos->next);
  141. SLT* del = pos->next;
  142. pos->next = pos->next->next;
  143. free(del);
  144. del = NULL;
  145. }

感谢各位大佬支持并指出问题,

如果本篇内容对你有帮助,可以一键三连支持以下,感谢支持!!!

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

闽ICP备14008679号