当前位置:   article > 正文

数据结构—单链表

数据结构—单链表

1、链表的概念及结构

1.1链表的概念

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

1.2链表的结构

如下图:

逻辑上的链表,pList是指向头个元素的指针 

物理上的链表

有人可能会有疑问,不是说链表只是在逻辑结构上是连续的,在物理存储结构上是不连续的,那为什么上图中一个个结点明明是挨在一起的,那么它在物理存储结构上肯定是连续的呀,其实不然,上图是为了方便大家理解,才用线条连接了结点,实际上在内存中,每个结点可能会隔得很远,仔细观察每个结点上面的红色文字,那就是这个结点的地址,而蓝色文字是下一个结点的地址,很明显能看到这两个结点并不是相邻的,因此也验证了顺序表在逻辑结构上确实是连续的,但在物理存储结构上确实是不连续的。

2、链表的实现

2.1 定义结点

介绍一下单链表的英文名——single linked list,我们简写成SL(区别于顺序表的SeqList或者SQL)。

注意:next指针的类型是SListNode*,千万不要写成SLTDataType*,因为next指针是结构体指针,是用来指向下一个结点的

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

2.2链表的功能

链表要实现那些功能呢?其实这些功能我们都很熟悉,数据结构无非是对数据进行管理,要实现数据的增删查改,因此链表的基本功能也都是围绕着数据的增删查改展开。

注意:链表是不需要初始化的

  1. //打印单链表
  2. void SLTPrint(SLTNode* phead);
  3. //创建一个结点
  4. SLTNode* BuyLTNode(SLTDataType x);
  5. //销毁单链表
  6. void SLTDestroy(SLTNode** pphead);
  7. //头插
  8. void SLPushFront(SLTNode** pphead, SLTDataType x);
  9. //尾插
  10. void SLPushBack(SLTNode** pphead, SLTDataType x);
  11. //头删
  12. void SLPopFront(SLTNode** pphead);
  13. //尾删
  14. void SLPopBack(SLTNode** pphead);
  15. // 单链表查找
  16. SLTNode* STFind(SLTNode* phead, SLTDataType x);
  17. // 在pos之前插入
  18. void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
  19. //在pos之后插入
  20. void SLInsertAfter(SLTNode* pos, SLTDataType x);
  21. // 删除pos位置的值
  22. void SLErase(SLTNode** pphead, SLTNode* pos);
  23. // 删除pos位置后面的值
  24. void SLEraseAfter(SLTNode* pos);
  25. // 单链表结点修改
  26. void SLTModify(SLTNode* phead, SLTNode* pos, SLTDataType x);

2.3链表功能实现

2.3.1打印单链表

注意:链表和顺序不同的是,顺序表传过来的指针是肯定不会为空的,而链表传过来的指针是可能为空的,比如说当链表中没有元素时,头指针所指向的就是NULL,如果在第一行写上断言就会有问题。

当cur指向空的时候就可以停止打印了。

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

2.3.2 创建一个新结点

后面我们要在单链表中进行头插和尾插,此时插入的不再是像顺序表一样简单的SLDataType数据了,而是一个结点,这个结点是包括SLTDataType数据以及SLTDataType*的指针,因此,为了方便和减少代码的重复度,我们另写一个函数用来专门创建新结点

  1. SLTNode* BuyLTNode(SLTDataType x)
  2. {
  3. SLTNode * newnode=(SLTNode*)malloc(sizeof(SLTNode));
  4. if(newnode==NULL)
  5. {
  6. perror("malloc fail");
  7. return NULL;
  8. }
  9. newnode->data=x;
  10. newnode->next=NULL;//初始化
  11. return newnode;
  12. }

2.3.3 单链表尾插

注意:在创建结点时,已经让 结点.next=NULL,所以不需要在插入完结点后,再让新结点的next指针为NULL。

有人可能会有疑问,为什么之前打印链表的时候不用断言指针,而在尾插时就要断言指针,以及为什么函数的形参是二级指针,而不使用一级指针。

因为,尾插分为两种情况(下面有写),当链表为空时,头指针phead指向NULL,尾插相当于头插,此时要改变phead的指向,让phead指向这个新结点,此时就需要二级指针来改变一级指针的值(如果我们用一级指针做形参,形参的改变不会影响实参,那么一级指针phead就不会被改变)。

至于这个什么时候要断言指针,什么时候不用断言指针:一级指针也就是phead,当链表为空的时候,phead就是为NULL,而二级指针永远指向phead,phead的地址是永远存在的,那么pphead就一定不可能为空,所以需要断言pphead。

  1. //尾插
  2. //第一个尾插需要二级指针,接下来就不用二级指针
  3. //第一次是改变结构的指针plist
  4. //第二次是改变结构体尾结点
  5. void SLPushBack(SLTNode** pphead, SLTDataType x)
  6. {
  7. SLTNode *newnode= BuyLTNode(x);
  8. //是否为空链表
  9. if(*pphead==NULL)
  10. *pphead=newnode;//改变结构的指针plist
  11. else
  12. {
  13. SLTNode *tail=*pphead;
  14. while(tail->next!=NULL)//找到链表最后一位,当tail->为空时,就把新开辟的链表节点接上去
  15. tail=tail->next;
  16. tail->next=newnode;//改变结构体尾结点
  17. //就是把newnode存放的新结点地址给tail->next,然后在存放在之前最后一个结点的struct SListNode* next;中,这样next指向的地址不是NULL,而是新加的结点的位置
  18. //不能用tail=newnode,因为tail和newnode是局部变量,当这函数结束后就释放了
  19. }
  20. }

2.2.4 单链表尾删

要想删除链表中的元素,就必须保证原链表就有元素,因此要断言assert(*pphead)

尾删需要分情况去讨论

  1. //尾删
  2. void SLPopBack(SLTNode** pphead)
  3. {
  4. //当没有结点(空链表)
  5. //暴力检查
  6. assert(*pphead);
  7. //温柔检查
  8. // if(*pphead==NULL)
  9. // return;
  10. //当只有一个结点时
  11. if((*pphead)->next==NULL)
  12. {
  13. free(*pphead);
  14. *pphead=NULL;
  15. }
  16. else
  17. {
  18. SLTNode *prev = NULL;//用来指向倒数第二个结点
  19. SLTNode *tail = *pphead;//用来释放最后一个指针空间
  20. //找尾
  21. while (tail->next) {
  22. prev = tail;
  23. tail = tail->next;
  24. }
  25. free(tail);//把最后一个指针空间释放掉
  26. prev->next = NULL;//把最后一个的前一个结点指针设为空
  27. //如果直接tail=NULL的话,倒数第二个结点就指向一个NULL,就成为了一个野指针
  28. //while(tail->next->next)//找到倒数第二个
  29. // tail=tail->next;
  30. //free(tail->next);//把倒数第二个结点指向的结构体释放掉
  31. //tail->next=NULL;//把倒数第二个结点置为空
  32. }
  33. }

2.2.5  单链表头删

头删没什么好说的,记住要断言*pphead,保证链表内容不为空。

  1. //头删
  2. void SLPopFront(SLTNode** pphead)
  3. {
  4. //空链表
  5. assert(*pphead);
  6. // //一个结点
  7. // if((*pphead)->next==NULL)
  8. // {
  9. // free(*pphead);
  10. // *pphead=NULL;
  11. // }
  12. // //多个结点
  13. // else
  14. // {
  15. // SLTNode *del=*pphead;
  16. // //*pphead=del->nest;
  17. // *pphead=(*pphead)->next;
  18. // free(del);
  19. // }
  20. SLTNode* del = *pphead;
  21. *pphead = (*pphead)->next;
  22. free(del);
  23. }

 2.2.6 单链表头插

现在是不是觉得头插很简单了啊!

  1. //头插
  2. void SLPushFront(SLTNode** pphead, SLTDataType x)
  3. {
  4. SLTNode *newnode= BuyLTNode(x);
  5. newnode->next= *pphead;
  6. *pphead=newnode;//都需要二级指针
  7. }

2.2.7 查找某个结点

这个函数返回值不再是void,而是SLTNode*,把找到的结点的地址返回去,这个函数一般会跟结点修改之类的函数一起使用。

  1. SLTNode* STFind(SLTNode* phead, SLTDataType x)
  2. {
  3. SLTNode *cur=phead;
  4. while (cur)
  5. {
  6. if(cur->data==x)
  7. {
  8. return cur;
  9. }
  10. cur=cur->next;
  11. }
  12. return NULL;
  13. }

2.2.8 删除某个节点

  1. // 删除pos位置的值
  2. void SLErase(SLTNode** pphead, SLTNode* pos)
  3. {
  4. assert(pphead);
  5. assert(pos);
  6. //如果只有一个结点
  7. if(pos==*pphead)
  8. SLPopFront(pphead);
  9. else
  10. {
  11. SLTNode *p1=*pphead;
  12. while(p1->next!=pos)
  13. p1=p1->next;
  14. p1->next=pos->next;
  15. free(pos);
  16. }
  17. }

注意:

  1. pos也要断言,pos可不能为空呀!
  2. cur->next也要断言,因为当cur->next为NULL时,说明整个链表的结点都排查完了,最后还是没有找到地址为pos的结点,证明pos传值有误。

 2.2.9单链表结点修改

  1. // 单链表结点修改
  2. void SLTModify(SLTNode* phead, SLTNode* pos, SLTDataType x)
  3. {
  4. SLTNode* cur=phead;
  5. while(cur!=pos)
  6. {
  7. cur=cur->next;
  8. assert(cur);
  9. }
  10. pos->data=x;
  11. }

2.2.10 单链表在pos位前插入结点

  1. // 在pos之前插入
  2. void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
  3. {
  4. assert(pphead);
  5. assert(pos);
  6. //如果只有一个结点
  7. if(*pphead==pos)
  8. SLPushFront(pphead,x);
  9. else
  10. {
  11. SLTNode *p1=*pphead;
  12. while(p1->next!=pos)
  13. {
  14. p1=p1->next;
  15. }
  16. SLTNode *newnode= BuyLTNode(x);
  17. p1->next=newnode;
  18. newnode->next=pos;
  19. }
  20. }

2.2.11单链表在pos位后插入结点

  1. //在pos之后插入
  2. void SLInsertAfter(SLTNode* pos, SLTDataType x)
  3. {
  4. assert(pos);
  5. SLTNode *newnode= BuyLTNode(x);
  6. newnode->next=pos->next;
  7. pos->next=newnode;
  8. }

2.2.12销毁链表

销毁链表这一块,咱可不敢直接free(phead),因为链表在物理结构上是不连续存储的,销毁链表必须要一个结点一个结点去销毁!!!!最后不要忘记把phead置为NULL。

  1. //单链表销毁
  2. void SListDesTroy(SLTNode** pphead)
  3. {
  4. assert(pphead && *pphead );
  5. SLTNode *pcur=*pphead;
  6. while(pcur)
  7. {
  8. SLTNode *next=pcur->next;
  9. free(pcur);
  10. pcur=next;
  11. }
  12. *pphead=NULL;
  13. }

3、总代码

3.1 SList.h

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<assert.h>
  4. typedef int SLTDataType;
  5. typedef struct SListNode
  6. {
  7. SLTDataType data;
  8. struct SListNode* next;
  9. }SLTNode;
  10. void SLTPrint(SLTNode* phead);
  11. //头插
  12. void SLPushFront(SLTNode** pphead, SLTDataType x);
  13. //尾插
  14. void SLPushBack(SLTNode** pphead, SLTDataType x);
  15. //头删
  16. void SLPopFront(SLTNode** pphead);
  17. //尾删
  18. void SLPopBack(SLTNode** pphead);
  19. // 单链表查找
  20. SLTNode* STFind(SLTNode* phead, SLTDataType x);
  21. // 在pos之前插入
  22. void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
  23. //在pos之后插入
  24. void SLInsertAfter(SLTNode* pos, SLTDataType x);
  25. // 删除pos位置的值
  26. void SLErase(SLTNode** pphead, SLTNode* pos);
  27. // 删除pos位置后面的值
  28. void SLEraseAfter(SLTNode* pos);
  29. //单链表销毁
  30. void SListDesTroy(SLTNode** pphead);
  31. // 单链表结点修改
  32. void SLTModify(SLTNode* phead, SLTNode* pos, SLTDataType x);

3.2 SList.c

  1. #include "SList.h"
  2. void SLTPrint(SLTNode* phead)
  3. {
  4. SLTNode* cur=phead;
  5. while(cur!=NULL)
  6. {
  7. printf("%d->",cur->data);
  8. cur=cur->next;
  9. }
  10. printf("NULL\n");
  11. }
  12. //创建一个新的动态内存
  13. SLTNode* BuyLTNode(SLTDataType x)
  14. {
  15. SLTNode * newnode=(SLTNode*)malloc(sizeof(SLTNode));
  16. if(newnode==NULL)
  17. {
  18. perror("malloc fail");
  19. return NULL;
  20. }
  21. newnode->data=x;
  22. newnode->next=NULL;//初始化
  23. return newnode;
  24. }
  25. //头插
  26. void SLPushFront(SLTNode** pphead, SLTDataType x)
  27. {
  28. SLTNode *newnode= BuyLTNode(x);
  29. newnode->next= *pphead;
  30. *pphead=newnode;//都需要二级指针
  31. }
  32. //尾插
  33. //第一个尾插需要二级指针,接下来就不用二级指针
  34. //第一次是改变结构的指针plist
  35. //第二次是改变结构体尾结点
  36. void SLPushBack(SLTNode** pphead, SLTDataType x)
  37. {
  38. SLTNode *newnode= BuyLTNode(x);
  39. //是否为空链表
  40. if(*pphead==NULL)
  41. *pphead=newnode;//改变结构的指针plist
  42. else
  43. {
  44. SLTNode *tail=*pphead;
  45. while(tail->next!=NULL)//找到链表最后一位,当tail->为空时,就把新开辟的链表节点接上去
  46. tail=tail->next;
  47. tail->next=newnode;//改变结构体尾结点
  48. //就是把newnode存放的新结点地址给tail->next,然后在存放在之前最后一个结点的struct SListNode* next;中,这样next指向的地址不是NULL,而是新加的结点的位置
  49. //不能用tail=newnode,因为tail和newnode是局部变量,当这函数结束后就释放了
  50. }
  51. }
  52. //头删
  53. void SLPopFront(SLTNode** pphead)
  54. {
  55. //空链表
  56. assert(*pphead);
  57. // //一个结点
  58. // if((*pphead)->next==NULL)
  59. // {
  60. // free(*pphead);
  61. // *pphead=NULL;
  62. // }
  63. // //多个结点
  64. // else
  65. // {
  66. // SLTNode *del=*pphead;
  67. // //*pphead=del->nest;
  68. // *pphead=(*pphead)->next;
  69. // free(del);
  70. // }
  71. SLTNode* del = *pphead;
  72. *pphead = (*pphead)->next;
  73. free(del);
  74. }
  75. //尾删
  76. void SLPopBack(SLTNode** pphead)
  77. {
  78. //当没有结点(空链表)
  79. //暴力检查
  80. assert(*pphead);
  81. //温柔检查
  82. // if(*pphead==NULL)
  83. // return;
  84. //当只有一个结点时
  85. if((*pphead)->next==NULL)
  86. {
  87. free(*pphead);
  88. *pphead=NULL;
  89. }
  90. else
  91. {
  92. SLTNode *prev = NULL;//用来指向倒数第二个结点
  93. SLTNode *tail = *pphead;//用来释放最后一个指针空间
  94. //找尾
  95. while (tail->next) {
  96. prev = tail;
  97. tail = tail->next;
  98. }
  99. free(tail);//把最后一个指针空间释放掉
  100. prev->next = NULL;//把最后一个的前一个结点指针设为空
  101. //如果直接tail=NULL的话,倒数第二个结点就指向一个NULL,就成为了一个野指针
  102. //while(tail->next->next)//找到倒数第二个
  103. // tail=tail->next;
  104. //free(tail->next);//把倒数第二个结点指向的结构体释放掉
  105. //tail->next=NULL;//把倒数第二个结点置为空
  106. }
  107. }
  108. // 单链表查找
  109. SLTNode* STFind(SLTNode* phead, SLTDataType x)
  110. {
  111. SLTNode *cur=phead;
  112. while (cur)
  113. {
  114. if(cur->data==x)
  115. {
  116. return cur;
  117. }
  118. cur=cur->next;
  119. }
  120. return NULL;
  121. }
  122. // 在pos之前插入
  123. void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
  124. {
  125. assert(pphead);
  126. assert(pos);
  127. //如果只有一个结点
  128. if(*pphead==pos)
  129. SLPushFront(pphead,x);
  130. else
  131. {
  132. SLTNode *p1=*pphead;
  133. while(p1->next!=pos)
  134. {
  135. p1=p1->next;
  136. }
  137. SLTNode *newnode= BuyLTNode(x);
  138. p1->next=newnode;
  139. newnode->next=pos;
  140. }
  141. }
  142. //在pos之后插入
  143. void SLInsertAfter(SLTNode* pos, SLTDataType x)
  144. {
  145. assert(pos);
  146. SLTNode *newnode= BuyLTNode(x);
  147. newnode->next=pos->next;
  148. pos->next=newnode;
  149. }
  150. // 删除pos位置的值
  151. void SLErase(SLTNode** pphead, SLTNode* pos)
  152. {
  153. assert(pphead);
  154. assert(pos);
  155. //如果只有一个结点
  156. if(pos==*pphead)
  157. SLPopFront(pphead);
  158. else
  159. {
  160. SLTNode *p1=*pphead;
  161. while(p1->next!=pos)
  162. p1=p1->next;
  163. p1->next=pos->next;
  164. free(pos);
  165. }
  166. }
  167. // 删除pos位置后面的值
  168. void SLEraseAfter(SLTNode* pos)
  169. {
  170. assert(pos);
  171. assert(pos->next);
  172. SLTNode *newnode=pos->next;
  173. pos->next=newnode->next;
  174. free(newnode);
  175. }
  176. //单链表销毁
  177. void SListDesTroy(SLTNode** pphead)
  178. {
  179. assert(pphead && *pphead );
  180. SLTNode *pcur=*pphead;
  181. while(pcur)
  182. {
  183. SLTNode *next=pcur->next;
  184. free(pcur);
  185. pcur=next;
  186. }
  187. *pphead=NULL;
  188. }
  189. // 单链表结点修改
  190. void SLTModify(SLTNode* phead, SLTNode* pos, SLTDataType x)
  191. {
  192. SLTNode* cur=phead;
  193. while(cur!=pos)
  194. {
  195. cur=cur->next;
  196. assert(cur);
  197. }
  198. pos->data=x;
  199. }

3.3 text.c

测试函数可以根据大家的想法进行测试,下面是我的测试函数以及输出情况

  1. #include"SList.h"
  2. void TestSList1()
  3. {
  4. SLTNode* plist = NULL;
  5. SLPushFront(&plist, 1);
  6. SLPushFront(&plist, 2);
  7. SLPushFront(&plist, 3);
  8. SLPushFront(&plist, 4);
  9. SLTPrint(plist);
  10. }
  11. void TestSList2()
  12. {
  13. SLTNode *plist=NULL;
  14. SLPushFront(&plist, 1);
  15. SLPushFront(&plist, 2);
  16. SLPushFront(&plist, 3);
  17. SLPushFront(&plist, 4);
  18. SLPushBack(&plist, 5);
  19. SLPushBack(&plist, 6);
  20. SLPushBack(&plist, 7);
  21. SLPushBack(&plist, 8);
  22. SLTPrint(plist);
  23. SLTNode *pos= STFind(plist,3);
  24. if(pos)
  25. {
  26. SLInsert(&plist,pos,30);
  27. }
  28. SLTPrint(plist);
  29. pos= STFind(plist,6);
  30. if(pos)
  31. {
  32. SLInsertAfter(plist,88);
  33. }
  34. SLTPrint(plist);
  35. pos= STFind(plist,7);
  36. if(pos)
  37. {
  38. SLErase(&plist,pos);
  39. }
  40. SLTPrint(plist);
  41. pos= STFind(plist,1);
  42. if(pos)
  43. {
  44. SLEraseAfter(pos);
  45. }
  46. SLTPrint(plist);
  47. SListDesTroy(&plist);
  48. }
  49. void Swapi(int* p1, int* p2)
  50. {
  51. int tmp = *p1;
  52. *p1 = *p2;
  53. *p2 = tmp;
  54. }
  55. void Swappi(int** pp1, int** pp2)
  56. {
  57. int* tmp = *pp1;
  58. *pp1 = *pp2;
  59. *pp2 = tmp;
  60. }
  61. int main()
  62. {
  63. // TestSList1();
  64. TestSList2();
  65. // int a = 0, b = 1;
  66. // Swapi(&a, &b);
  67. //
  68. // int* px = &a, * py = &b;
  69. // Swappi(&px, &py);
  70. return 0;
  71. }

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

闽ICP备14008679号