当前位置:   article > 正文

【数据结构】单向链表_求单链表节点数目的算法

求单链表节点数目的算法

哈喽,大家好,今天我们学习的是数据结构里的链表,这里主要讲的是不带哨兵卫头节点的单向链表,下篇将会继续带大家学习双向链表。

目录

1.链表的概念

2.单向链表接口的实现

2.1动态申请一个节点

2.2单链表打印

2.3单链表尾插

2.4单链表头插

2.5单链表尾删

2.6单链表头删

2.7在pos位置插入x

2.7.1在pos位置前插入x

2.7.2在pos位置后插入x

2.8删除pos位置值

2.9 查找x的地址

2.10销毁链表

3.完整代码


1.链表的概念

在上篇文章,我们已经学习了顺序表,不知大家有没有发现顺序表在一定程度上是存在缺陷的,比如说:

  1. 空间不够了的时候需要扩容,扩容需要付出代价(特别是异地扩空间)
  2. 为了避免频繁扩容,我们满了基本都是扩2倍,可能会导致一定的空间浪费
  3. 顺序表要求数据从开始位置连续存储,那么我们在头部或者中间位置插入删除数据就需要挪动数据,效率不高

针对顺序表的缺陷,就有了链表来存储数据

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

 链表的定义:

 这里的data就是要存放的数据

2.单向链表接口的实现

下面是要介绍的常用到的链表接口函数以及实现方法:

  1. //打印
  2. void SListPrint(SLTNode* phead);
  3. //创建新节点
  4. SLTNode* BuyListNode(SLTDateType x);
  5. //尾插
  6. void SListPushBack(SLTNode** pphead, SLTDateType x);
  7. //头插
  8. void SListPushFront(SLTNode** pphead, SLTDateType x);
  9. //头删
  10. void SListPopBack(SLTNode** pphead);
  11. //尾删
  12. void SListPopFront(SLTNode** pphead);
  13. //查找
  14. SLTNode* SListFind(SLTNode* phead, SLTDateType x);
  15. //插入
  16. void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x);
  17. void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDateType x);
  18. //删除
  19. void SListErase(SLTNode** pphead, SLTNode* pos);
  20. //销毁
  21. void SListDestroy(SLTNode** pphead);

2.1动态申请一个节点

由于我们每次给链表插入数据时,都需要动态开辟空间来申请节点,所以我们把这个过程封装成一个函数,方便后续操作。

动态申请一个节点的步骤是先向计算机内存申请一块空间,这里我们将申请的空间用指针变量newnode来存储,然后将newnode中的data赋值,因为这是新开辟的节点,所以暂时将newnode中的next指向空。

注意:为了提高程序的可靠性,我们在动态内存申请后记得检查是否申请失败了,如果申请失败了输出提示信息,并退出程序。

  1. SLTNode* BuyListNode(SLTDateType x)
  2. {
  3. SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  4. if (newnode == NULL)//如果动态内存申请失败就退出程序
  5. {
  6. printf("malloc fail\n");
  7. exit(-1);
  8. }
  9. newnode->data = x;
  10. newnode->next = NULL;
  11. return newnode;
  12. }

2.2单链表打印

打印链表就是一个遍历链表的过程,我们首先定义一个指针(cur)指向链表的头节点,然后输出该节点的值,然后将指针指向下一个节点(cur=cur->next),依次进行,直到cur为空指针时停止

  1. void SListPrint(SLTNode* phead)
  2. {
  3. SLTNode* cur = phead;
  4. while (cur != NULL)
  5. {
  6. printf("%d->", cur->data);
  7. cur = cur->next;//将cur指向下一个节点
  8. }
  9. printf("NULL\n");
  10. }

2.3单链表尾插

尾插,就是先找到链表中最后一个节点,然后将数据插入到最后。

但是,我们要先判断链表是否为空,如果链表为空,我们直接直接将链表的头指针赋予要插入的数据。

由于尾插要改变链表,所以传参要用二级指针,包括下面的尾插,尾删,头删等都要用二级指针传参

  1. void SListPushBack(SLTNode** pphead, SLTDateType x)
  2. {
  3. SLTNode* newnode = BuyListNode(x);
  4. if (*pphead == NULL)
  5. {
  6. *pphead = newnode;
  7. }
  8. else
  9. {
  10. SLTNode* tail = *pphead;
  11. while (tail->next != NULL)
  12. {
  13. tail = tail->next;
  14. }
  15. tail->next = newnode;
  16. }
  17. }

2.4单链表头插

头插是比较简单的一种操作,只需要申请新节点,将新节点的next指向链表的头,再让新节点成为链表的头即可。

  1. void SListPushFront(SLTNode** pphead, SLTDateType x)
  2. {
  3. SLTNode* newnode = BuyListNode(x);
  4. newnode->next = *pphead;
  5. *pphead = newnode;
  6. }

2.5单链表尾删

尾删:每次找到链表的最后一个节点和倒数第二个节点,然后释放最后一个节点所占的看空间并将最后一个节点置空,同时将倒数第二个节点的next指向NULL;如果链表只剩下一个节点,直接释放并置空该节点(这一步需要单独考虑)

注意:为了避免链表为空但有调用尾删的情况,我们需要断言一下,当传过来的链表是空链表的时候,程序就会报错

  1. void SListPopBack(SLTNode** pphead)
  2. {
  3. //保证链表不是空链表
  4. assert(*pphead != NULL);
  5. if ((*pphead)->next == NULL)
  6. {
  7. //当链表中只有一个节点
  8. free(*pphead);
  9. *pphead = NULL;
  10. }
  11. else
  12. {
  13. SLTNode* tail = *pphead;
  14. SLTNode* prev = NULL;
  15. while (tail->next != NULL)
  16. {
  17. prev = tail;
  18. tail = tail->next;
  19. }
  20. prev->next = NULL;
  21. free(tail);
  22. tail = NULL;
  23. }
  24. }

2.6单链表头删

头删是将第一个节点释放然后指向第二个节点,在此之前需要定义一个指针next来保存第二个节点的地址。

  1. void SListPopFront(SLTNode** pphead)
  2. {
  3. //保证链表不是空链表
  4. assert(*pphead != NULL);
  5. SLTNode* next = (*pphead)->next;
  6. free(*pphead);
  7. *pphead = next;
  8. }

2.7在pos位置插入x

在pos位置插入x分为两种情况,一种是在pos前位置插入x ,另一种是在pos后位置插入x,下面将分别为大家介绍:

2.7.1在pos位置前插入x

在pos位置前插入x,只需要找到pos的前一个位置,我们把pos的前一个位置命名为posPrev,然后创建一个新节点newnode,将posPrev的下一个节点指向newnodenewnode的下一个节点指向pos即可,如下图:

  1. void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
  2. {
  3. assert(pphead != NULL);
  4. assert(pos != NULL);
  5. SLTNode* newnode = BuyListNode(x);
  6. if (*pphead == pos)
  7. {
  8. newnode->next = *pphead;
  9. *pphead = newnode;
  10. }
  11. else
  12. {
  13. //找到pos的前一个位置
  14. SLTNode* posPrev = *pphead;
  15. while (posPrev->next != pos)
  16. {
  17. posPrev = posPrev->next;
  18. }
  19. posPrev->next = newnode;
  20. newnode->next = pos;
  21. }
  22. }

2.7.2在pos位置后插入x

在pos位置后插入x比在pos位置前插入x要简单,不需要遍历链表即可完成

  1. void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDateType x)
  2. {
  3.     assert(pos != NULL);
  4.     SLTNode* newnode = BuyListNode(x);
  5.     newnode->next = pos->next;
  6.     pos->next = newnode;
  7. }

2.8删除pos位置值

删除pos位置值也需要先找到pos的前一个节点,因此也要考虑pos是链表的头节点的情况

  1. void SListErase(SLTNode** pphead, SLTNode* pos)
  2. {
  3. assert(*pphead != NULL);
  4. assert(pos != NULL);
  5. if (*pphead == pos)
  6. {
  7. *pphead = pos->next;
  8. free(pos);
  9. }
  10. else
  11. {
  12. SLTNode* posPrev = *pphead;
  13. while (posPrev->next != pos)
  14. {
  15. posPrev = posPrev->next;
  16. }
  17. posPrev->next = pos->next;
  18. free(pos);
  19. }
  20. }

2.9 查找x的地址

查找x的地址,如果查找到了x,则返回该节点的地址,否则返回空指针。这个步骤也要遍历链表。

  1. SLTNode* SListFind(SLTNode* phead, SLTDateType x)
  2. {
  3. SLTNode* tail = phead;
  4. if (tail->data == x)
  5. {
  6. return tail;
  7. }
  8. while (tail ->data != x)
  9. {
  10. tail = tail->next;
  11. if (tail->data == x)
  12. {
  13. return tail;
  14. }
  15. }
  16. return NULL;
  17. }

2.10销毁链表

销毁链表需要将所有节点所占的内存全部释放,再将链表的头置为空即可。

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

3.完整代码

SList.h文件:

  1. #pragma once
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<assert.h>
  5. typedef int SLTDateType;
  6. typedef struct SListNode
  7. {
  8. SLTDateType data;
  9. struct SListNode* next;//存放下一个链表的地址
  10. }SLTNode;
  11. //打印
  12. void SListPrint(SLTNode* phead);
  13. //创建新节点
  14. SLTNode* BuyListNode(SLTDateType x);
  15. //尾插
  16. void SListPushBack(SLTNode** pphead, SLTDateType x);
  17. //头插
  18. void SListPushFront(SLTNode** pphead, SLTDateType x);
  19. //头删
  20. void SListPopBack(SLTNode** pphead);
  21. //尾删
  22. void SListPopFront(SLTNode** pphead);
  23. //查找
  24. SLTNode* SListFind(SLTNode* phead, SLTDateType x);
  25. //插入
  26. void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x);
  27. void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDateType x);
  28. //删除
  29. void SListErase(SLTNode** pphead, SLTNode* pos);
  30. //销毁
  31. void SListDestroy(SLTNode** pphead);

SList.c文件:

  1. #include"SList.h"
  2. void SListPrint(SLTNode* phead)
  3. {
  4. SLTNode* cur = phead;
  5. while (cur != NULL)
  6. {
  7. printf("%d->", cur->data);
  8. cur = cur->next;//将cur指向下一个节点
  9. }
  10. printf("NULL\n");
  11. }
  12. SLTNode* BuyListNode(SLTDateType x)
  13. {
  14. SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  15. if (newnode == NULL)//如果动态内存申请失败就退出程序
  16. {
  17. printf("malloc fail\n");
  18. exit(-1);
  19. }
  20. newnode->data = x;
  21. newnode->next = NULL;
  22. return newnode;
  23. }
  24. void SListPushBack(SLTNode** pphead, SLTDateType x)
  25. {
  26. SLTNode* newnode = BuyListNode(x);
  27. if (*pphead == NULL)
  28. {
  29. *pphead = newnode;
  30. }
  31. else
  32. {
  33. SLTNode* tail = *pphead;
  34. while (tail->next != NULL)
  35. {
  36. tail = tail->next;
  37. }
  38. tail->next = newnode;
  39. }
  40. }
  41. void SListPushFront(SLTNode** pphead, SLTDateType x)
  42. {
  43. SLTNode* newnode = BuyListNode(x);
  44. newnode->next = *pphead;
  45. *pphead = newnode;
  46. }
  47. void SListPopBack(SLTNode** pphead)
  48. {
  49. //保证链表不是空链表
  50. assert(*pphead != NULL);
  51. if ((*pphead)->next == NULL)
  52. {
  53. //当链表中只有一个节点
  54. free(*pphead);
  55. *pphead = NULL;
  56. }
  57. else
  58. {
  59. SLTNode* tail = *pphead;
  60. SLTNode* prev = NULL;
  61. while (tail->next != NULL)
  62. {
  63. prev = tail;
  64. tail = tail->next;
  65. }
  66. prev->next = NULL;
  67. free(tail);
  68. tail = NULL;
  69. }
  70. }
  71. void SListPopFront(SLTNode** pphead)
  72. {
  73. //保证链表不是空链表
  74. assert(*pphead != NULL);
  75. SLTNode* next = (*pphead)->next;
  76. free(*pphead);
  77. *pphead = next;
  78. }
  79. SLTNode* SListFind(SLTNode* phead, SLTDateType x)
  80. {
  81. SLTNode* tail = phead;
  82. if (tail->data == x)
  83. {
  84. return tail;
  85. }
  86. while (tail ->data != x)
  87. {
  88. tail = tail->next;
  89. if (tail->data == x)
  90. {
  91. return tail;
  92. }
  93. }
  94. return NULL;
  95. }
  96. void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
  97. {
  98. assert(pphead != NULL);
  99. assert(pos != NULL);
  100. SLTNode* newnode = BuyListNode(x);
  101. if (*pphead == pos)
  102. {
  103. newnode->next = *pphead;
  104. *pphead = newnode;
  105. }
  106. else
  107. {
  108. //找到pos的前一个位置
  109. SLTNode* posPrev = *pphead;
  110. while (posPrev->next != pos)
  111. {
  112. posPrev = posPrev->next;
  113. }
  114. posPrev->next = newnode;
  115. newnode->next = pos;
  116. }
  117. }
  118. void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDateType x)
  119. {
  120. assert(pos != NULL);
  121. SLTNode* newnode = BuyListNode(x);
  122. newnode->next = pos->next;
  123. pos->next = newnode;
  124. }
  125. void SListErase(SLTNode** pphead, SLTNode* pos)
  126. {
  127. assert(*pphead != NULL);
  128. assert(pos != NULL);
  129. if (*pphead == pos)
  130. {
  131. *pphead = pos->next;
  132. free(pos);
  133. }
  134. else
  135. {
  136. SLTNode* posPrev = *pphead;
  137. while (posPrev->next != pos)
  138. {
  139. posPrev = posPrev->next;
  140. }
  141. posPrev->next = pos->next;
  142. free(pos);
  143. }
  144. }
  145. void SListDestroy(SLTNode** pphead)
  146. {
  147. assert(*pphead != NULL);
  148. SLTNode* cur = *pphead;
  149. while (cur != NULL)
  150. {
  151. SLTNode* next = cur->next;
  152. free(cur);
  153. cur = next;
  154. }
  155. *pphead = NULL;
  156. }

总结:这篇文章主要写的是单向链表,后续将继续带领大家学习双向链表。如果我写的有什么的不好之处,请在文章下方给出你宝贵的意见。如果觉得我写的好的话请点个赞赞和关注哦~

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