当前位置:   article > 正文

数据结构:线性表之-单向链表(无头)_单项链表是什么专业

单项链表是什么专业

目录

什么是单向链表

顺序表和链表的区别和联系

顺序表:

链表:

链表表示(单项)和实现

1.1 链表的概念及结构

1.2单链表(无头)的实现

所用文件

将有以下功能:

链表定义

创建新链表元素

尾插

头插

尾删

头删

查找-给一个节点的指针

pos位置之前插入

删除pos位置的值

成品展示

SList.h

SList.c

test.c


什么是单向链表

单向链表是一种常见的线性数据结构,它由一系列节点组成,每个节点包含两部分:数据和指向下一个节点的指针。每个节点只能访问它后面的节点,而不能访问前面的节点。

单向链表的特点:

  • 每个节点包含数据和指向下一个节点的指针。
  • 最后一个节点的指针指向空值(NULL),表示链表的结束。
  • 可以动态地添加或删除节点,链表的长度可以根据需要进行扩展或缩小。
  • 可以根据指针迅速插入或删除节点,而不需要移动其他节点。

单向链表相对于数组来说,具有一些优点和缺点:

  • 优点:插入和删除元素的时间复杂度为O(1),不需要像数组一样进行元素的移动;链表长度可以动态调整,没有固定大小的限制。
  • 缺点:要访问特定位置的元素需要从头开始遍历,时间复杂度为O(n);相比于数组,在使用额外的指针存储下一个节点的信息,会占用更多的内存空间。

由于单向链表的特点,它常常被用于需要频繁插入和删除元素的场景,或者在事先无法确定数据大小和数量的情况下使用。

顺序表和链表的区别和联系

顺序表:

优点:
空间连续、支持随机访问
缺点:

  1. 中间或前面部分的插入删除时间复杂度O(N)
  2. 2.增容的代价比较大。

链表:

缺点:
以节点为单位存储,不支持随机访问
优点:

  1. 任意位置插入删除时间复杂度为O(1)
  2. 没有增容消耗,按需申请节点空间,不用了直接释放。

链表表示(单项)和实现

1.1 链表的概念及结构

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

1.2单链表(无头)的实现

 

  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
    构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都
    是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带
    来很多优势,实现反而简单了,后面我们代码实现了就知道了。

所用文件

定义三个文件:

  1. 头文件 SList.h
  2. 函数的实现SList.c
  3. 代码的测试test.c

将有以下功能:

  1. //打印链表
  2. void SListPrint(SLTNode* phead);
  3. //创建新链表元素(动态申请一个节点)
  4. SLTNode* BuySListNode(SLTDataType x);
  5. //尾插
  6. void SListPushBack(SLTNode** pphead, SLTDataType x);
  7. //头插
  8. void SListPushFront(SLTNode** pphead, SLTDataType x);
  9. //尾删
  10. void SListPopBack(SLTNode** pphead);
  11. //头删
  12. void SListPopFront(SLTNode** pphead);
  13. //查找->可在查找的基础上进行修改SListAlter
  14. SLTNode* SListFind(SLTNode* phead,SLTDataType x);
  15. //改
  16. void SListAlter(SLTNode* phead, SLTDataType x,SLTDataType y);
  17. //pos位置之前插入
  18. void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
  19. //删除pos位置的值
  20. void SListErase(SLTNode** pphead, SLTNode* pos);

链表定义

定义链表基本结构

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

创建新链表元素

创建新元素用于插入原链表

  1. SLTNode* BuySListNode(SLTDataType x)
  2. {
  3. //开辟空间
  4. SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  5. assert(newnode);
  6. newnode->data = x;
  7. newnode->next = NULL;
  8. return newnode;
  9. }

尾插

  1. void SListPushBack(SLTNode** pphead, SLTDataType x)
  2. {
  3. //开辟空间
  4. SLTNode* newnode = BuySListNode(x);
  5. if (*pphead == NULL)
  6. {
  7. //防止开始时节点为空
  8. *pphead = newnode;
  9. }
  10. else
  11. {
  12. //找尾节点
  13. SLTNode* tail = *pphead;//找到链表首元素
  14. while (tail->next != NULL)
  15. {
  16. //检索到未节点
  17. tail = tail->next;
  18. }
  19. //插入
  20. tail->next = newnode;
  21. }
  22. }

头插

  1. void SListPushFront(SLTNode** pphead, SLTDataType x)
  2. {
  3. SLTNode* newnode = BuySListNode(x);
  4. newnode->next = *pphead;
  5. *pphead = newnode;//地址传给pphead
  6. //*pphead=&plist
  7. /*
  8. 头插无需检查是否为空
  9. */
  10. }

尾删

  1. void SListPopBack(SLTNode** pphead)
  2. {
  3. assert(*pphead);
  4. if ((*pphead)->next==NULL)
  5. {
  6. //1,只有一个节点
  7. free(*pphead);
  8. *pphead = NULL;
  9. }
  10. else
  11. {
  12. //2,有多个节点
  13. //将前一个链元素中存放的地址换为NULL,防止野指针
  14. /* 写法一 */
  15. SLTNode* tailPrev = NULL;
  16. SLTNode* tail = *pphead;
  17. while (tail->next!=NULL)
  18. {
  19. tailPrev = tail;//tail的地址
  20. tail = tail->next;
  21. }
  22. free(tail);
  23. tailPrev->next = NULL;
  24. /* //写法二
  25. * //tail寻找的是倒数第二个元素
  26. while (tail->next->next!=NULL)
  27. {
  28. tail = tail->next;
  29. }
  30. free(tail->next);
  31. tail->next = NULL;
  32. */
  33. }
  34. }

头删

  1. void SListPopFront(SLTNode** pphead)
  2. {
  3. //防止被删空
  4. assert(*pphead);
  5. //找到首位链表元素
  6. SLTNode* next = (*pphead)->next;//存储首元素存放下一个元素的地址
  7. free(*pphead);//释放首元素
  8. *pphead = next;//将第二位元素改为首元素
  9. }

查找-给一个节点的指针

  1. //无需更改元素,故传一级指针
  2. SLTNode* SListFind(SLTNode* phead, SLTDataType x)
  3. {
  4. SLTNode* cur = phead;
  5. while (cur)
  6. {
  7. if (cur->data==x)
  8. return cur;
  9. cur = cur->next;
  10. }
  11. //未找到指定元素,返回NULL
  12. return NULL;
  13. }

改元素是建立再查找基础之上进行更改

  1. void SListAlter(SLTNode* phead, SLTDataType x, SLTDataType y)
  2. {
  3. printf("修改成功:\n");
  4. //先找到相应元素,再进行更改
  5. SLTNode* ret = SListFind(phead, y);
  6. ret->data = x;
  7. }

pos位置之前插入

任意位置之前插入

  1. void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
  2. {
  3. assert(pphead);
  4. assert(pos);
  5. //头插
  6. if (pos == *pphead)
  7. SListPushFront(pphead, x);
  8. else
  9. {
  10. //任意位置之前插入
  11. SLTNode* prev = *pphead;
  12. while (prev->next!=pos)//找到pos的位置
  13. {
  14. prev = prev->next;//prev存放pos的地址
  15. }
  16. //找到位置
  17. SLTNode* newnode = BuySListNode(x);//创建新链表元素,并赋值
  18. prev->next = newnode;//给前一个元素赋上下一元素地址
  19. newnode->next = pos;//给插入元素存放下一个元素的地址
  20. }
  21. }

删除pos位置的值

  1. void SListErase(SLTNode** pphead, SLTNode* pos)
  2. {
  3. assert(pphead);
  4. assert(pos);
  5. if (*pphead == pos)
  6. SListPopFront(pphead);//头删
  7. else
  8. {
  9. SLTNode* prev = *pphead;
  10. while (prev->next != pos)//找到pos的位置
  11. {
  12. prev = prev->next;//prev存放pos的地址
  13. //移到pos前一位,next存放的是pos的地址
  14. }
  15. //将prev存放的地址改为pos后一个元素的地址
  16. prev->next = pos->next;
  17. //释放pos
  18. free(pos);
  19. pos = NULL;
  20. }
  21. }

成品展示

SList.h

  1. #pragma once
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <assert.h>
  5. typedef int SLTDataType;
  6. typedef struct SListNode
  7. {
  8. int data;
  9. struct SListNode* next;
  10. }SLTNode;
  11. //打印链表
  12. void SListPrint(SLTNode* phead);
  13. //创建新链表元素(动态申请一个节点)
  14. SLTNode* BuySListNode(SLTDataType x);
  15. //尾插
  16. void SListPushBack(SLTNode** pphead, SLTDataType x);
  17. //头插
  18. void SListPushFront(SLTNode** pphead, SLTDataType x);
  19. //尾删
  20. void SListPopBack(SLTNode** pphead);
  21. //头删
  22. void SListPopFront(SLTNode** pphead);
  23. //查找->可在查找的基础上进行修改SListAlter
  24. SLTNode* SListFind(SLTNode* phead,SLTDataType x);
  25. void SListAlter(SLTNode* phead, SLTDataType x,SLTDataType y);
  26. //pos位置之前插入
  27. void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
  28. //删除pos位置的值
  29. void SListErase(SLTNode** pphead, SLTNode* pos);

SList.c

  1. #include "SList.h"
  2. //打印
  3. void SListPrint(SLTNode* phead)
  4. {
  5. SLTNode* cur = phead;
  6. while (cur!=NULL)
  7. {
  8. printf("%d->", cur->data);
  9. cur = cur->next;//存放下一个元素的地址
  10. }
  11. printf("NULL\n");
  12. }
  13. //创建新链表元素
  14. SLTNode* BuySListNode(SLTDataType x)
  15. {
  16. SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  17. assert(newnode);
  18. newnode->data = x;
  19. newnode->next = NULL;
  20. return newnode;
  21. }
  22. //尾插
  23. void SListPushBack(SLTNode** pphead, SLTDataType x)
  24. {
  25. assert(pphead);
  26. SLTNode* newnode = BuySListNode(x);//data
  27. if (*pphead == NULL)
  28. {
  29. //防止开始时节点为空
  30. *pphead = newnode;
  31. }
  32. else
  33. {
  34. //找尾节点
  35. SLTNode* tail = *pphead;
  36. while (tail->next != NULL)
  37. {
  38. //存放新节点地址
  39. tail = tail->next;
  40. }
  41. tail->next = newnode;
  42. }
  43. }
  44. //头插
  45. void SListPushFront(SLTNode** pphead, SLTDataType x)
  46. {
  47. assert(pphead);
  48. SLTNode* newnode = BuySListNode(x);
  49. newnode->next = *pphead;
  50. *pphead = newnode;//地址传给pphead
  51. //*pphead=&plist
  52. /*
  53. 头插无需检查是否为空
  54. */
  55. }
  56. //尾删
  57. void SListPopBack(SLTNode** pphead)
  58. {
  59. assert(*pphead);
  60. if ((*pphead)->next==NULL)
  61. {
  62. //1,只有一个节点
  63. free(*pphead);
  64. *pphead = NULL;
  65. }
  66. else
  67. {
  68. //2,有多个节点
  69. //将前一个链元素中存放的地址换为NULL,防止野指针
  70. /* 写法一 */
  71. SLTNode* tailPrev = NULL;
  72. SLTNode* tail = *pphead;
  73. while (tail->next!=NULL)
  74. {
  75. tailPrev = tail;//tail的地址
  76. tail = tail->next;
  77. }
  78. free(tail);
  79. tailPrev->next = NULL;
  80. /* //写法二
  81. * //tail寻找的是倒数第二个元素
  82. while (tail->next->next!=NULL)
  83. {
  84. tail = tail->next;
  85. }
  86. free(tail->next);
  87. tail->next = NULL;
  88. */
  89. }
  90. }
  91. //头删
  92. void SListPopFront(SLTNode** pphead)
  93. {
  94. //防止被删空
  95. assert(*pphead);
  96. SLTNode* next = (*pphead)->next;
  97. free(*pphead);
  98. *pphead = next;
  99. }
  100. //查找-给一个节点的指针
  101. SLTNode* SListFind(SLTNode* phead, SLTDataType x)
  102. {
  103. SLTNode* cur = phead;
  104. while (cur)
  105. {
  106. if (cur->data==x)
  107. return cur;
  108. cur = cur->next;
  109. }
  110. return NULL;
  111. }
  112. //改
  113. void SListAlter(SLTNode* phead, SLTDataType x, SLTDataType y)
  114. {
  115. assert(phead);
  116. printf("修改成功:\n");
  117. SLTNode* ret = SListFind(phead, y);
  118. ret->data = x;
  119. }
  120. //pos位置之前插入
  121. void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
  122. {
  123. assert(pphead);
  124. assert(pos);
  125. //头插
  126. if (pos == *pphead)
  127. SListPushFront(pphead, x);
  128. else
  129. {
  130. SLTNode* prev = *pphead;
  131. while (prev->next!=pos)//找到pos的位置
  132. {
  133. prev = prev->next;//prev存放pos的地址
  134. }
  135. //找到位置
  136. SLTNode* newnode = BuySListNode(x);//创建新链表元素,并赋值
  137. prev->next = newnode;//给前一个元素赋上下一元素地址
  138. newnode->next = pos;//给插入元素存放下一个元素的地址
  139. }
  140. }
  141. //删除pos位置的值
  142. void SListErase(SLTNode** pphead, SLTNode* pos)
  143. {
  144. assert(pphead);
  145. assert(pos);
  146. if (*pphead == pos)
  147. SListPopFront(pphead);//头删
  148. else
  149. {
  150. SLTNode* prev = *pphead;
  151. while (prev->next != pos)//找到pos的位置
  152. {
  153. prev = prev->next;//prev存放pos的地址
  154. //移到pos前一位,next存放的是pos的地址
  155. }
  156. //将prev存放的地址改为pos后一个元素的地址
  157. prev->next = pos->next;
  158. //释放pos
  159. free(pos);
  160. pos = NULL;
  161. }
  162. }

test.c

test.c仅仅是用于测试代码,本文以弄懂单向链表为主,故不进行菜单制作
但改测试中也包含了对部分链表结构即原理进行了讲解,请耐心看完

  1. #include "SList.h"
  2. void TestSList1()
  3. {
  4. SLTNode* n1 = (SLTNode*)malloc(sizeof(SLTNode));
  5. assert(n1);
  6. SLTNode* n2 = (SLTNode*)malloc(sizeof(SLTNode));
  7. assert(n2);
  8. SLTNode* n3 = (SLTNode*)malloc(sizeof(SLTNode));
  9. assert(n3);
  10. SLTNode* n4 = (SLTNode*)malloc(sizeof(SLTNode));
  11. assert(n4);
  12. n1->data=1;
  13. n2->data=2;
  14. n3->data=3;
  15. n4->data=4;
  16. n1->next = n2;
  17. n2->next = n3;
  18. n3->next = n4;
  19. n4->next = NULL;
  20. SListPrint(n1);
  21. }
  22. void TestSList2()
  23. {
  24. SLTNode* plist = NULL;
  25. //传的是plist指针的地址
  26. //如果直接传plist,将导致SLTNode* phead中
  27. //phead为临时拷贝,不影响实参
  28. SListPushBack(&plist, 1);
  29. SListPushBack(&plist, 2);
  30. SListPushBack(&plist, 3);
  31. SListPushBack(&plist, 4);
  32. SListPrint(plist);//不改变无需传二级指针
  33. SListPushFront(&plist,0);
  34. SListPrint(plist);
  35. SListPopFront(&plist);
  36. SListPrint(plist);
  37. SListPopBack(&plist);
  38. /*SListPrint(plist);
  39. SListPopBack(&plist);
  40. SListPrint(plist);
  41. SListPopBack(&plist);
  42. SListPrint(plist);
  43. SListPopBack(&plist);
  44. SListPrint(plist);*/
  45. /*SListPopBack(&plist);
  46. SListPrint(plist);*/
  47. }
  48. void TestSList3()
  49. {
  50. SLTNode* plist = NULL;
  51. //传的是plist指针的地址
  52. //如果直接传plist,将导致SLTNode* phead中
  53. //phead为临时拷贝,不影响实参
  54. SListPushBack(&plist, 1);
  55. SListPushBack(&plist, 2);
  56. SListPushBack(&plist, 3);
  57. SListPushBack(&plist, 4);
  58. SListPrint(plist);//不改变无需传二级指针
  59. //查找
  60. SLTNode* ret = SListFind(plist, 3);
  61. if (ret)
  62. {
  63. //返回值不为空则为找到
  64. printf("找到了\n");
  65. }
  66. SListPrint(plist);
  67. 修改
  68. //SListAlter(plist, 20, 2);
  69. //SListPrint(plist);
  70. //插入
  71. SLTNode* pos = SListFind(plist, 3);//先要找到再进行更改
  72. if (pos)
  73. {
  74. SListInsert(&plist, pos, 40);
  75. }
  76. SListPrint(plist);
  77. //删除
  78. pos = SListFind(plist, 2);//先要找到再进行删除
  79. if (pos)
  80. {
  81. SListErase(&plist, pos);
  82. }
  83. SListPrint(plist);
  84. }
  85. int main()
  86. {
  87. TestSList3();
  88. return 0;
  89. }

单向链表讲解完毕啦!创作不易,如果喜欢请留下个赞吧,感激不尽

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