当前位置:   article > 正文

【数据结构】手撕单链表

【数据结构】手撕单链表

目录

一,链表的概念及结构

二,接口实现

        1,单链表的创建

        2,接口函数

        3,动态创立新结点

        4,打印

        5,头插

        6,头删

        7,尾插

        8,尾删

        9,查找

        10,单链表在pos位置之后插入x

        11,单链表删除pos位置之后的值

        12,销毁

三,源代码

LKList.h

LKList.c

四,总结


一,链表的概念及结构

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

 

 

 而在数据结构中:

注意:

1,从上图可以看出,链式结构在逻辑上是连续的,但是在物理上不一定连续

 2,现实中的结点一般都是从堆上申请出来的

 3,从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续;

 实际中链表的结构非常多样,今天我们来写一下单链表,此表一会其他的自然水到渠成!

二,接口实现

        1,单链表的创建

  1. //无头 + 单向 + 非循环链表增删查改实现
  2. typedef int LKLDataType;
  3. typedef struct LinKedListNode
  4. {
  5. LKLDataType data;
  6. struct LinKedListNode* next;
  7. }LKLNode;

首先创建一个结构体表示单链表data是存储的值,LKLDataType是储存的值的数据类型,next是结点----指向下一个;

这里的LKLDataTypeint的重命名,也可以说是数据类型的重命名,这样统一化方便后续更改;

        2,接口函数

  1. // 动态创立新结点
  2. LKLNode* BuyLKLNode(LKLDataType x);
  3. // 打印
  4. void LKLPrint(LKLNode* phead);
  5. // 头插
  6. void LKLNodePushFront(LKLNode** phead, LKLDataType x);
  7. // 头删
  8. void LKLNodeBackFront(LKLNode** phead);
  9. // 尾插
  10. void LKLNodePushBack(LKLNode** phead, LKLDataType x);
  11. // 尾删
  12. void LKLNodePopBack(LKLNode** phead);
  13. // 查找
  14. LKLNode* LKLFind(LKLNode* phead, LKLDataType x);
  15. // 单链表在pos位置之后插入x
  16. void LKLInsertAfter(LKLNode* pos, LKLDataType x);
  17. // 单链表删除pos位置之后的值
  18. void LKLEraseAfter(LKLNode* pos);
  19. // 销毁
  20. void LKLDestroy(LKLNode** plist);

 这是以上要实现的接口函数;

        3,动态创立新结点

  1. //动态创立新结点
  2. LKLNode* BuyLKLNode(LKLDataType x)
  3. {
  4. LKLNode* newnode = (LKLNode*)malloc(sizeof(LKLNode));
  5. newnode->data = x;
  6. newnode->next = NULL;
  7. return newnode;
  8. }

后面创立新节点时直接调用此函数,一定要向堆区申请空间,这样函数结束空间会保留不会被回收;

        4,打印

  1. //打印
  2. void LKLPrint(LKLNode* phead)
  3. {
  4. assert(phead);
  5. LKLNode* cur = phead;
  6. while (cur)
  7. {
  8. printf("%d->", cur->data);
  9. cur = cur->next;
  10. }
  11. printf("NULL");
  12. }

打印也就是打印data的值,用cur=phead然后每次打印完都让cur走向下一个直到为空结束;

        5,头插

  1. //头插
  2. void LKLNodePushFront(LKLNode** phead, LKLDataType x)
  3. {
  4. assert(phead);
  5. LKLNode* newnode = BuyLKLNode(x);
  6. newnode->next = *phead;
  7. *phead = newnode;
  8. }

先断言一下,既然插入数据那就要申请一个新节点,然后令新节点的next指向phead然后再令phead指向新节点;

        6,头删

  1. //头删
  2. void LKLNodeBackFront(LKLNode** phead)
  3. {
  4. assert(phead);
  5. //为空
  6. assert(*phead);
  7. //非空
  8. LKLNode* newnode = (*phead)->next;
  9. free(*phead);
  10. *phead = newnode;
  11. }

还是先断言,有人会问为什么要断言两次?其实很好判断,哪个需要解引用那个就需要断言;

令一个变量newnode等于头结点的下一个,在释放头结点,在令头结点指向newnode即可;

        7,尾插

  1. //尾插
  2. void LKLNodePushBack(LKLNode** phead, LKLDataType x)
  3. {
  4. assert(phead);
  5. assert(*phead);
  6. LKLNode* newnode= BuyLKLNode(x);
  7. LKLNode* cur = *phead;
  8. //为空
  9. if (*phead == NULL)
  10. {
  11. *phead = newnode;
  12. }
  13. //非空
  14. else
  15. {
  16. while (cur->next)
  17. {
  18. cur = cur->next;
  19. }
  20. cur->next = newnode;
  21. }
  22. }

还是先断言判断,然后要插入一个新的数据先申请一个新结点,如果头结点为空则直接让头结点指向新结点即可,如果头结点不为空,则需要找到next为空的结点,这里用一个循环搞定,然后再直接让next为空的结点指向新节点即可;

        8,尾删

  1. //尾删
  2. void LKLNodePopBack(LKLNode** phead)
  3. {
  4. assert(phead);
  5. //为空
  6. assert(*phead);
  7. //一个
  8. if ((*phead)->next==NULL)
  9. {
  10. free(*phead);
  11. *phead = NULL;
  12. }
  13. //两个及以上
  14. else
  15. {
  16. LKLNode* tail = *phead;
  17. /*LKLNode* prev = NULL;
  18. while (tail->next)
  19. {
  20. prev = tail;
  21. tail = tail->next;
  22. }
  23. free(prev->next);
  24. prev->next = NULL;*/
  25. while (tail->next->next)
  26. {
  27. tail = tail->next;
  28. }
  29. free(tail->next);
  30. tail->next = NULL;
  31. }
  32. }

还是先断言一下,然后这里有两种情况链表只有一个结点,两个以上结点的时候

当链表只有一个结点也就是头结点,直接头删即可;

两个以上结点的时候,我这里有两种解决方案;

方案一常规法:先用循环找到next为空的结点,并且在循环里保留上一个结点prev,然后释放next为空的结点再让prevnext指向空即可;

方案二:不需要标记上一个结点,直接原地判断,判断结点的nextnext是否为空,否则继续向后推进,是则释放结点的next然后再令自己的next指向空也就相当于变成了尾结点

        9,查找

  1. // 单链表查找
  2. LKLNode* LKLFind(LKLNode* phead, LKLDataType x)
  3. {
  4. assert(phead);
  5. LKLNode* pos = phead;
  6. while (pos)
  7. {
  8. if (pos->data == x)
  9. {
  10. return pos;
  11. }
  12. pos = pos->next;
  13. }
  14. return NULL;
  15. }

老样子先断言一下,然后直接用循环遍历链表找到datax的值然后返回此结点即可;

        10,单链表在pos位置之后插入x

  1. // 单链表在pos位置之后插入x
  2. void LKLInsertAfter(LKLNode* pos, LKLDataType x)
  3. {
  4. assert(pos);
  5. LKLNode* newnode= BuyLKLNode(x);
  6. LKLNode* cur = pos;
  7. newnode->next = cur->next;
  8. cur->next = newnode;
  9. }

先断言,要插入数据先申请一个新结点,然后令新结点的next指向posnext,再返回来让posnext指向新结点;

        11,单链表删除pos位置之后的值

  1. // 单链表删除pos位置之后的值
  2. void LKLEraseAfter(LKLNode* pos)
  3. {
  4. assert(pos);
  5. assert(pos->next);
  6. LKLNode* cur = pos;
  7. LKLNode* newnode = cur->next->next;
  8. free(cur->next);
  9. cur->next = newnode;
  10. }

要删除值首先要确保得有值,所以开始断言;

先定义一个变量newnode指向posnextnext,然后再释放posnext,再令pos指向newnode以达到删除之后的效果;

        12,销毁

  1. // 单链表的销毁
  2. void LKLDestroy(LKLNode** phead)
  3. {
  4. assert(phead);
  5. assert(*phead);
  6. LKLNode* cur = *phead;
  7. LKLNode* next = NULL;
  8. while (cur)
  9. {
  10. next = cur->next;
  11. free(cur);
  12. cur = next;
  13. }
  14. }

老样子那个需要解引用那个就先断言一下,然后用循环遍历,先标记下一个结点,然后释放自己,再让自己指向标记的结点直到为空结束;

三,源代码

LKList.h

  1. #pragma once
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<assert.h>
  5. //无头 + 单向 + 非循环链表增删查改实现
  6. typedef int LKLDataType;
  7. typedef struct LinKedListNode
  8. {
  9. LKLDataType data;
  10. struct LinKedListNode* next;
  11. }LKLNode;
  12. // 动态创立新结点
  13. LKLNode* BuyLKLNode(LKLDataType x);
  14. // 打印
  15. void LKLPrint(LKLNode* phead);
  16. // 头插
  17. void LKLNodePushFront(LKLNode** phead, LKLDataType x);
  18. // 头删
  19. void LKLNodeBackFront(LKLNode** phead);
  20. // 尾插
  21. void LKLNodePushBack(LKLNode** phead, LKLDataType x);
  22. // 尾删
  23. void LKLNodePopBack(LKLNode** phead);
  24. // 查找
  25. LKLNode* LKLFind(LKLNode* phead, LKLDataType x);
  26. // 单链表在pos位置之后插入x
  27. void LKLInsertAfter(LKLNode* pos, LKLDataType x);
  28. // 单链表删除pos位置之后的值
  29. void LKLEraseAfter(LKLNode* pos);
  30. // 销毁
  31. void LKLDestroy(LKLNode** plist);

LKList.c

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"LKList.h"
  3. //动态创立新结点
  4. LKLNode* BuyLKLNode(LKLDataType x)
  5. {
  6. LKLNode* newnode = (LKLNode*)malloc(sizeof(LKLNode));
  7. newnode->data = x;
  8. newnode->next = NULL;
  9. return newnode;
  10. }
  11. //头插
  12. void LKLNodePushFront(LKLNode** phead, LKLDataType x)
  13. {
  14. assert(phead);
  15. LKLNode* newnode = BuyLKLNode(x);
  16. newnode->next = *phead;
  17. *phead = newnode;
  18. }
  19. //头删
  20. void LKLNodeBackFront(LKLNode** phead)
  21. {
  22. assert(phead);
  23. //为空
  24. assert(*phead);
  25. //非空
  26. LKLNode* newnode = (*phead)->next;
  27. free(*phead);
  28. *phead = newnode;
  29. }
  30. //尾插
  31. void LKLNodePushBack(LKLNode** phead, LKLDataType x)
  32. {
  33. assert(phead);
  34. assert(*phead);
  35. LKLNode* newnode= BuyLKLNode(x);
  36. LKLNode* cur = *phead;
  37. //为空
  38. if (*phead == NULL)
  39. {
  40. *phead = newnode;
  41. }
  42. //非空
  43. else
  44. {
  45. while (cur->next)
  46. {
  47. cur = cur->next;
  48. }
  49. cur->next = newnode;
  50. }
  51. }
  52. //尾删
  53. void LKLNodePopBack(LKLNode** phead)
  54. {
  55. assert(phead);
  56. //为空
  57. assert(*phead);
  58. //一个
  59. if ((*phead)->next==NULL)
  60. {
  61. free(*phead);
  62. *phead = NULL;
  63. }
  64. //两个及以上
  65. else
  66. {
  67. LKLNode* tail = *phead;
  68. /*LKLNode* prev = NULL;
  69. while (tail->next)
  70. {
  71. prev = tail;
  72. tail = tail->next;
  73. }
  74. free(prev->next);
  75. prev->next = NULL;*/
  76. while (tail->next->next)
  77. {
  78. tail = tail->next;
  79. }
  80. free(tail->next);
  81. tail->next = NULL;
  82. }
  83. }
  84. // 单链表查找
  85. LKLNode* LKLFind(LKLNode* phead, LKLDataType x)
  86. {
  87. assert(phead);
  88. LKLNode* pos = phead;
  89. while (pos)
  90. {
  91. if (pos->data == x)
  92. {
  93. return pos;
  94. }
  95. pos = pos->next;
  96. }
  97. return NULL;
  98. }
  99. // 单链表在pos位置之后插入x
  100. void LKLInsertAfter(LKLNode* pos, LKLDataType x)
  101. {
  102. assert(pos);
  103. LKLNode* newnode= BuyLKLNode(x);
  104. LKLNode* cur = pos;
  105. newnode->next = cur->next;
  106. cur->next = newnode;
  107. }
  108. // 单链表删除pos位置之后的值
  109. void LKLEraseAfter(LKLNode* pos)
  110. {
  111. assert(pos);
  112. assert(pos->next);
  113. LKLNode* cur = pos;
  114. LKLNode* newnode = cur->next->next;
  115. free(cur->next);
  116. cur->next = newnode;
  117. }
  118. // 单链表的销毁
  119. void LKLDestroy(LKLNode** phead)
  120. {
  121. assert(phead);
  122. assert(*phead);
  123. LKLNode* cur = *phead;
  124. LKLNode* next = NULL;
  125. while (cur)
  126. {
  127. next = cur->next;
  128. free(cur);
  129. cur = next;
  130. }
  131. }
  132. //打印
  133. void LKLPrint(LKLNode* phead)
  134. {
  135. assert(phead);
  136. LKLNode* cur = phead;
  137. while (cur)
  138. {
  139. printf("%d->", cur->data);
  140. cur = cur->next;
  141. }
  142. printf("NULL");
  143. }

四,总结

做数据结构的题目画图很重要,小伙伴们刚开始喜欢用脑子去构图想象,但遇到复杂的情况会紊乱的,画图最为可观方便,可以培养一个良好的画图习惯;

如有不足之处欢迎来补充交流!

完结。。。


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

闽ICP备14008679号