当前位置:   article > 正文

(超详细!)【C语言】单链表的增删查改(附图解,源码)_简易单链表及其删除操作c语言程序

简易单链表及其删除操作c语言程序

单链表学习导航

一.前言

二.准备工作

1.对单链表运行原理的简单理解

2.区域化编辑

 三.SList.h头文件引用区

1.单链表节点的创建

2.单链表功能函数的声明

四.SListTest.c测试区

五.SList.c功能实现区

1.动态申请一个节点

2.单链表打印

3.单链表尾插

4.单链表头插

5.单链表尾删

6.单链表头删

7.单链表查找

8.单链表在pos(指定)位置之后插入

9.单链表在pos(指定)位置之后删除

10.销毁单链表

六.源码

1.SList.h源码

2.SList.c源码

 七.结语


一.前言

本文为我对链表学习的过程与理解,适合新手学习交流。内容如题,我将结合画图和截图带领大家更好的理解单链表结构,学习和使用它增删查改的功能。(VS2019编译器所展示)

二.准备工作

1.对单链表运行原理的简单理解

 如上图,单链表是通过结构体里面放置一个新的结构体指针的形式,使用下一个结构体的地址来找到该结构体中的数据的,链表相比于顺序表具有存储数据不连续,方便修改的特点。但单链表的缺点也很明显,只能从第一个节点开始遍历,比较局限。

2.区域化编辑

此目的是为了更好的理解代码,在前文中我也提到过,但模块化看起来清晰更有利于我们理解以及使用、修改代码。(还有美观...)

 三.SList.h头文件引用区

1.单链表节点的创建

在该文件中,我们将链表结构体定义,设置int为链表中数据的存储形式(方便展示),同时节点中增加下一个节点的地址。重定义链表方便使用该结构体。

  1. #define SLTDateType int
  2. typedef struct SListNode
  3. {
  4. SLTDateType Data;
  5. struct SListNode* Next;
  6. }SListNode;

2.单链表功能函数的声明

在头文件中声明这些函数后直接使用即可,先列出来目标,后面咱们一一实现。

  1. // 动态申请一个节点
  2. SListNode* BuySListNode(SLTDateType x);
  3. // 单链表打印
  4. void SListPrint(SListNode* phead);
  5. // 单链表尾插
  6. void SListPushBack(SListNode** pplist, SLTDateType x);
  7. // 单链表的头插
  8. void SListPushFront(SListNode** pplist, SLTDateType x);
  9. // 单链表的尾删
  10. void SListPopBack(SListNode** pplist);
  11. // 单链表头删
  12. void SListPopFront(SListNode** pplist);
  13. // 单链表查找
  14. SListNode* SListFind(SListNode* plist, SLTDateType x);
  15. // 单链表在pos位置之后插入x
  16. void SListInsertAfter(SListNode* pos, SLTDateType x);
  17. // 单链表删除pos位置之后的值
  18. void SListEraseAfter(SListNode* pos);
  19. // 单链表的销毁
  20. void SListDestroy(SListNode* plist);

其中,有的函数功能传入的参数为二级指针,为什么呢?因为函数传入的参数是实参的一份临时拷贝,如果传入的是一级指针,那么我们可以改变这个指针指向的数据,而如果我们想要改变这个指针的地址呢?同样的道理,我们需要一个指向这个指针的地址的指针,也就是二级指针。

四.SListTest.c测试区

该区域主要为所写功能函数的测试模块,以此来判断函数的正确性、是否完善。

  1. int main()
  2. {
  3. SListNode* List = NULL;
  4. SListTest1(List);
  5. SListTest2(List);
  6. SListTest3(List);
  7. SListTest4(List);
  8. SListTest5(List);
  9. SListTest6(List);
  10. SListTest7(List);
  11. return 0;
  12. }

类似于菜单,不过少了些富丽堂皇的界面,因为写了菜单之后很可能会影响到我们调试的效率,将功能函数实现并调试完成之后,再完成菜单界面是个更好的选择。测试区域可以自行定义,我也将在下文中结合该区域来展示函数的实现效果。

五.SList.c功能实现区

1.动态申请一个节点

链表节点的申请可以使用malloc和calloc函数,此处我们使用malloc,记得判断返回值是否为空。

  1. //动态申请链表节点
  2. SListNode* BuySListNode(SLTDateType x)
  3. {
  4. SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
  5. if (newnode == NULL)
  6. {
  7. perror("BuySListNode::malloc");
  8. return;
  9. }
  10. newnode->Data = x;
  11. return newnode;
  12. }

此处还可以在返回节点之前将该节点的下一个地址置空,初始化也是种不错的选择。可以看个人需求选择。

2.单链表打印

将第一个节点传入函数,通过节点中的下一个指针一一遍历每个节点中存储的数据,并打印出来,最后节点为空时停止即可。

  1. //打印链表
  2. void SListPrint(SListNode* phead)
  3. {
  4. SListNode* cur = phead;
  5. while (cur)
  6. {
  7. printf("%d->", cur->Data);
  8. cur = cur->Next;
  9. }
  10. printf("NULL\n");
  11. }

3.单链表尾插

尾插需要考虑两种情况,第一种则是链表为空的情况,这种情况直接让该链表指向开辟的节点上即可;第二种情况为非空的情况,这种情况比较复杂,我们来画图解决:

 地址都是瞎编的,原理如此,代码实现如下:

  1. //尾插
  2. void SListPushBack(SListNode** pplist, SLTDateType x)
  3. {
  4. //判断是否为空链表
  5. if (*pplist==NULL)
  6. {
  7. *pplist = BuySListNode(x);
  8. (*pplist)->Next = NULL;
  9. }
  10. else
  11. {
  12. //查找尾部
  13. SListNode* ptr = *pplist;
  14. while (ptr->Next)
  15. {
  16. ptr = ptr->Next;
  17. }
  18. //插入
  19. ptr->Next = BuySListNode(x);
  20. ptr->Next->Next = NULL;
  21. }
  22. }

结合打印链表函数效果如图:

4.单链表头插

头插的实现和尾插一样,分空链表和非空链表两种情况。二者区别在于非空情况,头插演示如图:

原来没有头插的非空链表,pplist指向头节点的地址。

插入头节点的非空链表演示如上图,代码实现如下图。

  1. //头插
  2. void SListPushFront(SListNode** pplist, SLTDateType x)
  3. {
  4. if (*pplist == NULL)
  5. {
  6. *pplist = BuySListNode(x);
  7. (*pplist)->Next = NULL;
  8. }
  9. else
  10. {
  11. SListNode* ptr = BuySListNode(x);
  12. ptr->Next = *pplist;
  13. *pplist = ptr;
  14. }
  15. }

实现效果如图:

5.单链表尾删

尾删分为三种情况:

第一种为链表是否为空,若为空,可以选择报错也可以选择直接return(没有内容咋删...);

第二种为链表不为空,但只有一个节点的情况,这种情况我们需要直接将链表置空;

第三中为多节点的情况,这时我们要将NULL节点之前的节点置空,其中需要NULL节点前前个节点来判断,因为还要free掉不要用的内存,该情况如下图所示。

具体实现如下:

  1. //尾删
  2. void SListPopBack(SListNode** pplist)
  3. {
  4. assert(pplist);
  5. assert(*pplist);
  6. if ((*pplist)->Next == NULL)
  7. {
  8. //单个链表块
  9. free(*pplist);
  10. *pplist = NULL;
  11. }
  12. else
  13. {
  14. //2个及以上链表块
  15. SListNode* ptr = *pplist;
  16. while (ptr->Next->Next)
  17. {
  18. ptr = ptr->Next;
  19. }
  20. free(ptr->Next);
  21. ptr->Next = NULL;
  22. }
  23. }

效果如下:

6.单链表头删

头删只需要考虑两种情况就行了。

第一种为判断链表是否为空;

第二种不论是否为多节点,指向头节点地址的指针都会向后移动到下一个节点的地址上,因此不需要考虑这下一个节点是否为空。

 代码实现如下:

  1. //头删
  2. void SListPopFront(SListNode** pplist)
  3. {
  4. assert(pplist);
  5. assert(*pplist);
  6. SListNode* Del = *pplist;
  7. *pplist = (*pplist)->Next;
  8. free(Del);
  9. Del = NULL;
  10. }

具体效果如图:

7.单链表查找

查找目标节点并返回该节点的地址,传值选用一级指针就行了,因为有返回值为一级指针,这类函数的方法也可以代替上面的二级指针的使用(个人觉得这样不好看)。

查找需要遍历,除非找到目标节点,否则到NULL为止停下,直接返回查找节点即可。

  1. //查找
  2. SListNode* SListFind(SListNode* plist, SLTDateType x)
  3. {
  4. SListNode* find = plist;
  5. while (find && find->Data != x)
  6. find = find->Next;
  7. return find;
  8. }

实现效果如图:

8.单链表在pos(指定)位置之后插入

这个函数和头插函数多节点的情况很像,不过该函数不需要改变头节点指针的位置,因此传入一级指针即可。

插入前需判断是否传入的为空指针,然后插入就行啦,不过写入的顺序要正确,否则可能会丢失原先的地址哦?

  1. //在pos位置之后插入x
  2. void SListInsertAfter(SListNode* pos, SLTDateType x)
  3. {
  4. assert(pos);
  5. SListNode* next = pos->Next;
  6. SListNode* newnode = BuySListNode(x);
  7. pos->Next = newnode;
  8. newnode->Next = next;
  9. }

效果如下:

9.单链表在pos(指定)位置之后删除

这个函数还比较简单,注意传值是否为空指针和指向的下一节点是否为空就行了。

没有判断后者的话可是会非法访问的...不得不说链表对于初学者而言坑太多了。

  1. //删除pos位置之后的值
  2. void SListEraseAfter(SListNode* pos)
  3. {
  4. assert(pos);
  5. if (pos->Next == NULL)
  6. return;
  7. else
  8. {
  9. SListNode* NNext = pos->Next->Next;
  10. free(pos->Next);
  11. pos->Next = NNext;
  12. }
  13. }

实现效果如下:

10.销毁单链表

类似于顺序表的销毁,不同的是需要free掉前者,且遍历的是链表节点的指针。

  1. //销毁
  2. void SListDestroy(SListNode* plist)
  3. {
  4. assert(plist);
  5. SListNode* Del = plist;
  6. while (Del)
  7. {
  8. plist = plist->Next;
  9. free(Del);
  10. Del = plist;
  11. }
  12. }

六.源码

1.SList.h源码

  1. #pragma once
  2. #include <stdio.h>
  3. #include <assert.h>
  4. #include <stdlib.h>
  5. #define int SLTDateType
  6. typedef struct SListNode
  7. {
  8. SLTDateType Data;
  9. struct SListNode* Next;
  10. }SListNode;
  11. // 动态申请一个节点
  12. SListNode* BuySListNode(SLTDateType x);
  13. // 单链表打印
  14. void SListPrint(SListNode* phead);
  15. // 单链表尾插
  16. void SListPushBack(SListNode** pplist, SLTDateType x);
  17. // 单链表的头插
  18. void SListPushFront(SListNode** pplist, SLTDateType x);
  19. // 单链表的尾删
  20. void SListPopBack(SListNode** pplist);
  21. // 单链表头删
  22. void SListPopFront(SListNode** pplist);
  23. // 单链表查找
  24. SListNode* SListFind(SListNode* plist, SLTDateType x);
  25. // 单链表在pos位置之后插入x
  26. void SListInsertAfter(SListNode* pos, SLTDateType x);
  27. // 单链表删除pos位置之后的值
  28. void SListEraseAfter(SListNode* pos);
  29. // 单链表的销毁
  30. void SListDestroy(SListNode* plist);

2.SList.c源码

  1. #include "SList.h"
  2. //动态申请链表节点
  3. SListNode* BuySListNode(SLTDateType x)
  4. {
  5. SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
  6. if (newnode == NULL)
  7. {
  8. perror("BuySListNode::malloc");
  9. return;
  10. }
  11. newnode->Data = x;
  12. return newnode;
  13. }
  14. //打印链表
  15. void SListPrint(SListNode* phead)
  16. {
  17. SListNode* cur = phead;
  18. while (cur)
  19. {
  20. printf("%d->", cur->Data);
  21. cur = cur->Next;
  22. }
  23. printf("NULL\n");
  24. }
  25. //尾插
  26. void SListPushBack(SListNode** pplist, SLTDateType x)
  27. {
  28. if (*pplist==NULL)
  29. {
  30. *pplist = BuySListNode(x);
  31. (*pplist)->Next = NULL;
  32. }
  33. else
  34. {
  35. //查找尾部
  36. SListNode* ptr = *pplist;
  37. while (ptr->Next)
  38. {
  39. ptr = ptr->Next;
  40. }
  41. //插入
  42. ptr->Next = BuySListNode(x);
  43. ptr->Next->Next = NULL;
  44. }
  45. }
  46. //头插
  47. void SListPushFront(SListNode** pplist, SLTDateType x)
  48. {
  49. assert(pplist);
  50. if (*pplist == NULL)
  51. {
  52. *pplist = BuySListNode(x);
  53. (*pplist)->Next = NULL;
  54. }
  55. else
  56. {
  57. SListNode* ptr = BuySListNode(x);
  58. ptr->Next = *pplist;
  59. *pplist = ptr;
  60. }
  61. }
  62. //尾删
  63. void SListPopBack(SListNode** pplist)
  64. {
  65. assert(pplist);
  66. assert(*pplist);
  67. if ((*pplist)->Next == NULL)
  68. {
  69. //单个链表块
  70. free(*pplist);
  71. *pplist = NULL;
  72. }
  73. else
  74. {
  75. //2个及以上链表块
  76. SListNode* ptr = *pplist;
  77. while (ptr->Next->Next)
  78. {
  79. ptr = ptr->Next;
  80. }
  81. free(ptr->Next);
  82. ptr->Next = NULL;
  83. }
  84. }
  85. //头删
  86. void SListPopFront(SListNode** pplist)
  87. {
  88. assert(pplist);
  89. assert(*pplist);
  90. SListNode* Del = *pplist;
  91. *pplist = (*pplist)->Next;
  92. free(Del);
  93. Del = NULL;
  94. }
  95. //查找
  96. SListNode* SListFind(SListNode* plist, SLTDateType x)
  97. {
  98. SListNode* find = plist;
  99. while (find && find->Data != x)
  100. find = find->Next;
  101. return find;
  102. }
  103. //在pos位置之后插入x
  104. void SListInsertAfter(SListNode* pos, SLTDateType x)
  105. {
  106. assert(pos);
  107. SListNode* next = pos->Next;
  108. SListNode* newnode = BuySListNode(x);
  109. pos->Next = newnode;
  110. newnode->Next = next;
  111. }
  112. //删除pos位置之后的值
  113. void SListEraseAfter(SListNode* pos)
  114. {
  115. assert(pos);
  116. if (pos->Next == NULL)
  117. return;
  118. else
  119. {
  120. SListNode* NNext = pos->Next->Next;
  121. free(pos->Next);
  122. pos->Next = NNext;
  123. }
  124. }
  125. //销毁
  126. void SListDestroy(SListNode* plist)
  127. {
  128. assert(plist);
  129. SListNode* Del = plist;
  130. while (Del)
  131. {
  132. plist = plist->Next;
  133. free(Del);
  134. Del = plist;
  135. }
  136. }

 七.结语

链表内容对于新手而言还是比较多的,坑也是无处不在,需要诸位的细心与耐心,除了单链表还有很多其他形式的,不过都大同小异,自己多手搓几遍就能弄清楚了,总之,我会和各位一起加油的!

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号