当前位置:   article > 正文

数据结构——单链表

数据结构——单链表

目录

一、链表

二、单链表的实现

2.1 打印单链表的内容

2.2 开辟新的节点

2.3 单链表的尾插

2.4 单链表的头插

2.5 单链表的尾删

2.6 单链表的头删

2.7 单链表的查找

2.8 在指定位置之前插入数据

2.9 在指定位置之后插入数据

2.10 删除指定的节点

2.11 删除指定的节点之后节点

2.12.销毁单链表

三、完整代码

总结


前言

之前我们了解了数据结构中线性表的顺序表的知识,今天我们来了解线性表中链表的知识。


一、链表

  链表是⼀种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的 。
  链表的结构跟火车车厢相似,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加⼏节。只需要将火车⾥的某节车厢去掉/加上,不会影响其他车厢,每节车厢都是独立存在的。、
链表的结构跟火车车厢相似,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉/加上,不会影响其他⻋厢,每节车厢都是独立存在的。
⻋厢是独立存在的,且每节车厢
都有车门。想象⼀下这样的场景,假设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带⼀把钥匙的情况下如何从车头走到车尾?
最简单的做法:每节车厢里都放⼀把下⼀节车厢的钥匙。
在链表⾥,每节“车厢”是什么样的呢?
补充说明:
1、链式机构在 逻辑上是连续 的,在 物理结构上不⼀定连续
2、节点⼀般是从 堆上 申请的
3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续
链表中每个节点都是独立申请的(即需要插入数据时才去申请⼀块节点的空间),我们需要通过指针变量来 保存下⼀个节点位置 才能从当前节点找到下⼀个节点。
结合前面学到的结构体知识,我们可以给出每个节点对应的结构体代码:
假设当前保存的节点为整型
  1. struct SListNode
  2. {
  3. int data; //节点数据
  4. struct SListNode* next; //指针变量⽤保存下⼀个节点的地址
  5. };
当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数
据,也需要保存下⼀个节点的地址(当下⼀个节点为空时保存的地址为空)。
当我们想要从第⼀个节点走到最后⼀个节点时,只需要在前⼀个节点拿上下⼀个节点的地址(下⼀个节点的钥匙)就可以了。

二、单链表的实现

单链表也是链表的一种,表示不带头单向不循环链表。
主要接口:
  1. //SList.h
  2. #pragma once
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<assert.h>
  6. //打印单链表的内容
  7. void SLTPrint(SLTNode* phead);
  8. //头部插⼊删除/尾部插⼊删除
  9. void SLTPushBack(SLTNode** pphead, SLTDataType x);
  10. void SLTPushFront(SLTNode** pphead, SLTDataType x);
  11. void SLTPopBack(SLTNode** pphead);
  12. void SLTPopFront(SLTNode** pphead);
  13. //查找
  14. SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);
  15. //在指定位置之前插⼊数据
  16. void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
  17. //删除pos节点
  18. void SLTErase(SLTNode** pphead, SLTNode* pos);
  19. //在指定位置之后插⼊数据
  20. void SLTInsertAfter(SLTNode* pos, SLTDataType x);
  21. //删除pos之后的节点
  22. void SLTEraseAfter(SLTNode* pos);
  23. //销毁链表
  24. void SListDesTroy(SLTNode** pphead);

以上是我们单链表的主要接口,同时我们要定义一个单链表的结构:

  1. //Slist.h
  2. typedef int SLTDataType;//存放数据的类型
  3. typedef struct SListNode {
  4. SLTDataType data;
  5. struct SListNode* next;//下一个节点的地址
  6. }SLTNode;

2.1 打印单链表的内容

  1. void SLTPrint(SLTNode* phead)
  2. {
  3. SLTNode* pcur = phead;
  4. while (pcur)
  5. {
  6. printf("%d->", pcur->data);
  7. pcur = pcur->next;
  8. }
  9. printf("NULL");
  10. }

通过pcur来实现对整个链表的遍历,但最后一个节点时,此时pucr->next=NULL,再赋值给pucr,所以跳出循环。然后每次循环打印每个节点中的数据。

2.2 开辟新的节点

对于插入操作,我们需要一个新的节点:

  1. //新的节点
  2. SLTNode* SLTBuyNode(SLTDataType x) {
  3. SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  4. if (newnode == NULL) {
  5. perror("malloc fail!");
  6. exit(1);
  7. }
  8. newnode->data =x;//节点数据为x
  9. newnode->next = NULL;///指针变量⽤保存下⼀个节点的地址为空
  10. return newnode;
  11. }

2.3 单链表的尾插

  1. void SLTPushBack(SLTNode** pphead, SLTDataType x) {
  2. assert(pphead);
  3. SLTNode* newnode=SLTBuyNode(x);//要插入新的节点
  4. if (*pphead == NULL) {//如果链表为空
  5. *pphead = newnode;//头节点为新节点
  6. return;
  7. }
  8. SLTNode* pucr = *pphead;//pucr遍历链表
  9. while (pucr->next)//如果为最后一个节点
  10. {
  11. pucr = pucr->next;//遍历
  12. }
  13. pucr->next = newnode;//最后一个节点的next为新节点
  14. }

尾插法就是遍历整个链表,找到最后一个节点,然后再插入同时要考虑链表为空的情况。

2.4 单链表的头插

  1. void SLTPushFront(SLTNode** pphead, SLTDataType x) {
  2. assert(pphead);
  3. SLTNode* newnode = SLTBuyNode(x);
  4. newnode->next = *pphead;//把新节点的下一个节点设为以前的头节点
  5. *pphead = newnode;//头节点变为新节点
  6. }

头插法比尾插相比更加快速,因为不用遍历整个链表。

2.5 单链表的尾删

  1. void SLTPopBack(SLTNode** pphead) {
  2. assert(pphead);
  3. assert(*pphead);//链表不为空
  4. if ((*pphead)->next == NULL) {//只有一个节点
  5. free(*pphead);
  6. *pphead = NULL;
  7. return;
  8. }
  9. SLTNode* ptail = *pphead;//遍历整个链表
  10. SLTNode* prev = NULL;//遍历时前一个节点
  11. while (ptail->next != NULL) {//最后一个节点
  12. prev = ptail;
  13. ptail = ptail->next;
  14. }
  15. prev->next = NULL;//尾删
  16. free(ptail);//释放最后一个节点
  17. ptail = NULL;
  18. }

尾删与尾插一样,都是遍历整个链表,找到最后一个节点,只不过操作不一样。

2.6 单链表的头删

  1. void SLTPopFront(SLTNode** pphead) {
  2. assert(pphead);
  3. assert(*pphead);//链表不为空
  4. SLTNode* ptail = *pphead;
  5. *pphead = ptail->next;//头节点为下一个节点
  6. free(ptail);//释放头节点
  7. ptail = NULL;
  8. }

我们需要一个指针来存放以前头节点的地址,不然到时候释放找不到地址了。

2.7 单链表的查找

  1. SLTNode* SLTFind(SLTNode** pphead, SLTDataType x) {
  2. assert(pphead);
  3. SLTNode* pucr =* pphead;//遍历
  4. while (pucr) {//直到最后一个节点
  5. if (pucr->data == x) {
  6. return pucr;//找到了返回节点
  7. }
  8. pucr = pucr->next;
  9. }
  10. return NULL;//没找到返回空
  11. }

找到了我们返回的是SLTNode*的节点类型。

2.8 在指定位置之前插入数据

  1. void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
  2. assert(pphead);
  3. assert(pos);//节点不为空
  4. assert(*pphead);//节点不为空了,链表也不能为空
  5. SLTNode* newNode=SLTBuyNode(x);//插入的新的节点
  6. if (pos == *pphead) {//在第一个节点插入
  7. SLTPushFront(pphead, x);//调用头插操作
  8. return;
  9. }
  10. SLTNode* prev = *pphead;//遍历
  11. while(prev->next != pos){//直到找到了目标节点之前的节点
  12. prev = prev->next;
  13. }
  14. //修改对应的指针连接
  15. prev->next = newNode;
  16. newNode->next = pos;
  17. }

如图:

2.9 在指定位置之后插入数据

  1. void SLTInsertAfter(SLTNode* pos, SLTDataType x) {
  2. assert(pos);
  3. SLTNode* newNode = SLTBuyNode(x);
  4. //直接修改pos后的节点链接
  5. newNode->next = pos->next;
  6. pos->next = newNode;
  7. }
注意:我们要先链接newNode后的链接,不能先把pos和newNode链接,不然会找不到pos后面的节点。

2.10 删除指定的节点

  1. void SLTErase(SLTNode** pphead, SLTNode* pos) {
  2. assert(pphead);
  3. assert(pos);//删除节点不为空
  4. assert(*pphead);//链表不为空
  5. SLTNode* prev = *pphead;
  6. if (prev == pos) {//如果删除头节点
  7. SLTPopFront(pphead);//调用头删
  8. return;
  9. }
  10. while (prev->next != pos) {//找到删除前的节点
  11. prev = prev->next;
  12. }
  13. //删除节点之前的节点链接到删除节点之后的节点
  14. prev->next = pos->next;
  15. //释放删除节点
  16. free(pos);
  17. pos = NULL;
  18. }

我们要注意不能先释放,不然找不到pos的下一个节点

2.11 删除指定的节点之后节点

  1. void SLTEraseAfter(SLTNode* pos) {
  2. assert(pos);
  3. assert(pos->next);//删除节点之后的节点不为空
  4. SLTNode* del = pos->next;//保存之后的节点
  5. pos->next = pos->next->next;//pos的下一个节点为下下个节点
  6. //释放之前保存的节点
  7. free(del);
  8. del = NULL;
  9. }

要保存目标节点之后的节点,不然释放就会出现问题。

2.12.销毁单链表

  1. void SListDesTroy(SLTNode** pphead) {
  2. assert(pphead);
  3. assert(*pphead);
  4. SLTNode* pucr = *pphead;
  5. while (pucr) {//循环销毁
  6. SLTNode* next = pucr->next;
  7. free(pucr);
  8. pucr =next;
  9. }
  10. *pphead = NULL;
  11. }

由于每个节点都是我们动态开辟出来的,所以我们要进行依次的销毁

三、完整代码

Slist.h

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

Slist.c

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"Slist.h"
  3. //打印单链表的内容
  4. void SLTPrint(SLTNode* phead)
  5. {
  6. SLTNode* pcur = phead;
  7. while (pcur)
  8. {
  9. printf("%d->", pcur->data);
  10. pcur = pcur->next;
  11. }
  12. printf("NULL");
  13. }
  14. //新的节点
  15. SLTNode* SLTBuyNode(SLTDataType x) {
  16. SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  17. if (newnode == NULL) {
  18. perror("malloc fail!");
  19. exit(1);
  20. }
  21. newnode->data =x;
  22. newnode->next = NULL;
  23. return newnode;
  24. }
  25. //尾插
  26. void SLTPushBack(SLTNode** pphead, SLTDataType x) {
  27. assert(pphead);
  28. SLTNode* newnode=SLTBuyNode(x);
  29. if (*pphead == NULL) {
  30. *pphead = newnode;
  31. return;
  32. }
  33. SLTNode* pucr = *pphead;
  34. while (pucr->next)
  35. {
  36. pucr = pucr->next;
  37. }
  38. pucr->next = newnode;
  39. }
  40. //头插
  41. void SLTPushFront(SLTNode** pphead, SLTDataType x) {
  42. assert(pphead);
  43. SLTNode* newnode = SLTBuyNode(x);
  44. newnode->next = *pphead;
  45. *pphead = newnode;
  46. }
  47. //尾删
  48. void SLTPopBack(SLTNode** pphead) {
  49. assert(pphead);
  50. assert(*pphead);
  51. if ((*pphead)->next == NULL) {
  52. free(*pphead);
  53. *pphead = NULL;
  54. return;
  55. }
  56. SLTNode* ptail = *pphead;
  57. SLTNode* prev = NULL;
  58. while (ptail->next != NULL) {
  59. prev = ptail;
  60. ptail = ptail->next;
  61. }
  62. prev->next = NULL;
  63. free(ptail);
  64. ptail = NULL;
  65. }
  66. //头删
  67. void SLTPopFront(SLTNode** pphead) {
  68. assert(pphead);
  69. assert(*pphead);
  70. SLTNode* ptail = *pphead;
  71. *pphead = ptail->next;
  72. free(ptail);
  73. ptail = NULL;
  74. }
  75. //查找
  76. SLTNode* SLTFind(SLTNode** pphead, SLTDataType x) {
  77. assert(pphead);
  78. SLTNode* pucr =* pphead;
  79. while (pucr) {
  80. if (pucr->data == x) {
  81. return pucr;
  82. }
  83. pucr = pucr->next;
  84. }
  85. return NULL;
  86. }
  87. 在指定位置之前插⼊数据
  88. void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
  89. assert(pphead);
  90. assert(pos);
  91. assert(*pphead);
  92. SLTNode* newNode=SLTBuyNode(x);
  93. if (pos == *pphead) {
  94. SLTPushFront(pphead, x);
  95. return;
  96. }
  97. SLTNode* prev = *pphead;
  98. while(prev->next != pos){
  99. prev = prev->next;
  100. }
  101. prev->next = newNode;
  102. newNode->next = pos;
  103. }
  104. //在指定位置之后插⼊数据
  105. void SLTInsertAfter(SLTNode* pos, SLTDataType x) {
  106. assert(pos);
  107. SLTNode* newNode = SLTBuyNode(x);
  108. newNode->next = pos->next;
  109. pos->next = newNode;
  110. }
  111. //删除pos节点
  112. void SLTErase(SLTNode** pphead, SLTNode* pos) {
  113. assert(pphead);
  114. assert(pos);
  115. assert(*pphead);
  116. SLTNode* prev = *pphead;
  117. if (prev == pos) {
  118. SLTPopFront(pphead);
  119. return;
  120. }
  121. while (prev->next != pos) {
  122. prev = prev->next;
  123. }
  124. prev->next = pos->next;
  125. free(pos);
  126. pos = NULL;
  127. }
  128. //删除pos之后的节点
  129. void SLTEraseAfter(SLTNode* pos) {
  130. assert(pos);
  131. assert(pos->next);
  132. SLTNode* del = pos->next;
  133. pos->next = pos->next->next;
  134. free(del);
  135. del = NULL;
  136. }
  137. //销毁链表
  138. void SListDesTroy(SLTNode** pphead) {
  139. assert(pphead);
  140. assert(*pphead);
  141. SLTNode* pucr = *pphead;
  142. while (pucr) {
  143. SLTNode* next = pucr->next;
  144. free(pucr);
  145. pucr =next;
  146. }
  147. *pphead = NULL;
  148. }

test.c

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"Slist.h"
  3. void Slisttest1()
  4. {
  5. SLTNode * pf =NULL;
  6. SLTPushBack(&pf, 1);
  7. SLTPushBack(&pf, 2);
  8. SLTPushBack(&pf, 3);
  9. SLTPushBack(&pf, 4);
  10. SLTPushFront(&pf, 5);
  11. SLTPushFront(&pf, 7);
  12. SLTPushFront(&pf, 6);
  13. SLTPushFront(&pf, 8);
  14. //SLTPopFront(&pf);
  15. SLTNode *p=SLTFind(&pf,8);
  16. //在指定位置前后插入数据
  17. //SLTInsert(&pf, p, 0);
  18. //SLTInsertAfter(p, 5);
  19. //SLTErase(&pf, p);
  20. //SLTEraseAfter(p);
  21. SListDesTroy(&pf);
  22. SLTPrint(pf);
  23. }
  24. int main() {
  25. Slisttest1();
  26. return 0;
  27. }

总结

上述文章我们讲了单链表的实现,希望对你有所帮助。

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

闽ICP备14008679号