当前位置:   article > 正文

【数据结构】单链表详解_链表头文件

链表头文件

目录

一、链表的定义和分类

1.链表的定义

2.链表的分类

二、单链表的创建

三、单链表函数的接口实现

1.头文件SListNode.h

2.源文件SListNode.c

3.源文件test.c


一、链表的定义和分类

1.链表的定义

  链表是指物理地址不连续,逻辑上连续的一种数据结构,可以通过指针访问各节点。

2.链表的分类

分类标准:单向和双向;带(哨兵位)头节点和不带(哨兵位)头节点;循环和不循环。

总共可以分为8类。

 

  虽然分类比较多,但常用的链表是普通的单链表和双向带头循环链表。

(双向带头循环链表在下篇博客中会进行介绍。)

二、单链表的创建

单链表由一个个节点连接而成,节点要包含的信息除了一个有效值之外,还需包含下一个节点的地址。故节点的实现需要利用结构体。

节点的创建:

  1. typedef int SLTDataType;
  2. struct SListNode
  3. {
  4. SLTDataType data;//有效值
  5. struct SListNode* next;//下一个节点的地址
  6. };

三、单链表函数的接口实现

1.头文件SListNode.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. {
  8. SLTDataType data;
  9. struct SListNode* next;
  10. }SListNode;
  11. // 动态申请一个节点
  12. SListNode* BuySListNode(SLTDataType x);
  13. // 单链表打印
  14. void SListPrint(SListNode* plist);
  15. // 单链表尾插
  16. void SListPushBack(SListNode** pplist, SLTDataType x);
  17. // 单链表的头插
  18. void SListPushFront(SListNode** pplist, SLTDataType x);
  19. // 单链表的尾删
  20. void SListPopBack(SListNode** pplist);
  21. // 单链表头删
  22. void SListPopFront(SListNode** pplist);
  23. // 单链表查找
  24. SListNode* SListFind(SListNode* plist, SLTDataType x);
  25. // 单链表在pos位置之后插入x
  26. // 分析思考为什么不在pos位置之前插入?
  27. void SListInsertAfter(SListNode* pos, SLTDataType x);
  28. // 单链表删除pos位置之后的值
  29. // 分析思考为什么不删除pos位置?
  30. void SListEraseAfter(SListNode* pos);
  31. // 单链表的销毁
  32. void SListDestroy(SListNode* plist);

2.源文件SListNode.c

进行单链表增删查改接口函数的实现(定义)。

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include "SListNode.h"
  3. // 动态申请一个节点
  4. SListNode* BuySListNode(SLTDataType x)
  5. {
  6. //用malloc在堆区上动态申请一个节点
  7. SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
  8. //初始化节点内容
  9. newnode->data = x;
  10. newnode->next = NULL;
  11. }
  12. // 单链表打印
  13. void SListPrint(SListNode* plist)
  14. {
  15. SListNode* cur = plist;
  16. //遍历链表
  17. while (cur != NULL)
  18. {
  19. printf("%d->", cur->data);
  20. cur = cur->next;
  21. }
  22. printf("NULL\n");
  23. }
  24. // 单链表尾插
  25. //链表尾插前不需要断言,断言是为了保证指针不为空;
  26. //而链表为空时也能插入节点
  27. //但是因为链表为空时插入节点,需要将新的节点的地址赋值给plist,
  28. //所以形参必须为二级指针,用来接收plist的地址
  29. void SListPushBack(SListNode** pplist, SLTDataType x)
  30. {
  31. //创建一个data值为x的节点
  32. SListNode* tmp = BuySListNode(x);
  33. //链表为空
  34. if (*pplist == NULL)
  35. {
  36. *pplist = tmp;
  37. return;
  38. }
  39. //链表不为空
  40. SListNode* cur = *pplist;
  41. while (cur->next != NULL)
  42. {
  43. cur = cur->next;
  44. }
  45. cur->next = tmp;
  46. }
  47. // 单链表的头插
  48. //需要更新头节点,在更新之前先将原头节点的地址赋值给新节点的next,实现逻辑上的连接
  49. void SListPushFront(SListNode** pplist, SLTDataType x)
  50. {
  51. //创建一个data值为x的节点
  52. SListNode* tmp = BuySListNode(x);
  53. tmp->next = *pplist;
  54. *pplist = tmp;
  55. }
  56. // 单链表的尾删
  57. //链表尾删前需要断言,断言是为了保证指针不为空;
  58. //因为链表为空时不能进行删除节点的操作。
  59. //因为链表只有一个节点时删除节点节点,需要将plist置为空指针,
  60. //所以形参必须为二级指针,用来接收plist的地址。
  61. void SListPopBack(SListNode** pplist)
  62. {
  63. assert(*pplist);//避免链表为空
  64. //链表只有一个节点
  65. if ((*pplist)->next == NULL)
  66. {
  67. free(*pplist);
  68. *pplist = NULL;
  69. }
  70. //链表有两个及以上节点
  71. SListNode* tail = *pplist;
  72. while (tail->next->next != NULL)//tail最后指向倒数第二个节点
  73. {
  74. tail = tail->next;
  75. }
  76. free(tail->next);
  77. tail->next = NULL;
  78. }
  79. // 单链表头删
  80. //与尾删相同的原因,头删也需要先断言,避免链表为空。
  81. //头删需要改变plist的值,所以需要传二级指针,
  82. //记录第二个节点的地址后,将第一个节点的空间释放;
  83. //最后将第二个节点的地址赋值给plist(若原链表只有一个节点,该操作后plist为空,故不需要分类特别处理。
  84. void SListPopFront(SListNode** pplist)
  85. {
  86. //避免链表为空
  87. assert(*pplist);
  88. SListNode* tmp = (*pplist)->next;//记录第二个节点的位置(只有一个节点则该指针置为NULL
  89. (*pplist)->next = NULL;
  90. free(*pplist);
  91. *pplist = tmp;
  92. }
  93. // 单链表查找
  94. //遍历链表,找到了返回对应节点的地址;遍历整个链表还没找到就返回NULL
  95. SListNode* SListFind(SListNode* plist, SLTDataType x)
  96. {
  97. assert(plist);//避免链表为空
  98. SListNode* cur = plist;
  99. while (cur != NULL)
  100. {
  101. if (cur->data == x)
  102. return cur;
  103. cur = cur->next;
  104. }
  105. return NULL;
  106. }
  107. //单链表在pos位置之后插入x
  108. //需要断言
  109. //分析思考为什么不在pos位置之前插入:
  110. //原因:能够通过将data值为x的节点的地址传给pos位置节点的next,
  111. //而我们无法直接得到pos位置前一个节点的next,需要遍历链表才能做到,效率低
  112. void SListInsertAfter(SListNode* pos, SLTDataType x)
  113. {
  114. assert(pos);
  115. SListNode* newnode = BuySListNode(x);
  116. newnode->next = pos->next;
  117. pos->next = newnode;
  118. }
  119. // 单链表删除pos位置之后的值
  120. // 分析思考为什么不删除pos位置?
  121. //删除pos位置需要将pos-1位置的next置为pos+1的地址,但是无发通过pos访问到pos-1位置
  122. void SListEraseAfter(SListNode* pos)
  123. {
  124. assert(pos);
  125. if (pos->next == NULL)
  126. return;
  127. SListNode* tmp = pos->next;
  128. pos->next = pos->next->next;
  129. //tmp->next = NULL; 不要这个也可以,为什么可以?
  130. free(tmp);
  131. }
  132. // 单链表的销毁
  133. //遍历链表进行节点的销毁,遍历一个释放一个节点的空间;
  134. //指针不用置空,因为cur为局部变量,函数调用结束后就会销毁
  135. void SListDestroy(SListNode** pplist)
  136. {
  137. assert(*pplist);
  138. SListNode* cur = *pplist;
  139. while (cur)
  140. {
  141. SListNode* tmp_next = cur->next;//记录下一个节点的地址
  142. free(cur);
  143. cur = tmp_next;
  144. }
  145. }

3.源文件test.c

测试接口函数的实现是否正确。

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include "SListNode.h"
  3. int main()
  4. {
  5. SListNode* plist = NULL;
  6. //测试尾插函数 测试成功
  7. SListPushBack(&plist, 3);
  8. SListPushBack(&plist, 2);
  9. SListPushBack(&plist, 1);
  10. //打印链表内容
  11. SListPrint(plist);//3->2->1->NULL
  12. //测试头插函数 测试成功
  13. SListPushFront(&plist, 4);
  14. SListPushFront(&plist, 5);
  15. SListPushFront(&plist, 6);
  16. //打印链表内容
  17. SListPrint(plist);//6->5->4->3->2->1->NULL
  18. //测试尾删函数 测试成功
  19. SListPopBack(&plist);
  20. SListPopBack(&plist);
  21. //打印链表内容
  22. SListPrint(plist);//6->5->4->3->NULL
  23. //测试头删函数 测试成功
  24. SListPopFront(&plist);
  25. SListPopFront(&plist);
  26. //打印链表内容
  27. SListPrint(plist);//4->3->NULL
  28. //测试查找函数 测试成功
  29. SListNode* pos = SListFind(plist,3);
  30. //SListNode* pos = SListFind(plist,5));
  31. if (pos)
  32. printf("链表中有该元素\n");
  33. else
  34. printf("链表中没有该元素\n");
  35. //测试在pos位置之后插入x的函数 测试成功
  36. SListInsertAfter(plist->next, 2);
  37. //打印链表内容
  38. SListPrint(plist);//4->3->2->NULL
  39. //测试删除位置之后的值的函数 测试成功
  40. SListEraseAfter(plist);
  41. //打印链表内容
  42. SListPrint(plist);//4->2->NULL
  43. //销毁动态申请的内存
  44. SListDestroy(&plist);//如果plist已经为空链表了,assert会报错
  45. plist = NULL;//避免野指针
  46. return 0;
  47. }

四、总结

单链表较顺序表而言,头插头删效率更高(时间复杂度O(1)),缺点是不能随机访问链表元素。其次,链表只能从前向后访问,后续博客会讲到的双向带头循环俩表就可以做到从后往前访问,更加灵活方便。

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

闽ICP备14008679号