• 数据结构的无头单向链表


    无头单向链表的数据元素之间逻辑上的线性关系通过在节点中专用的指针来体现,每个节点包含两部分一部分为数据,另一部分为指针。存储数据元素信息值的为数据域,存放其后继节点地址指针的为指针域

    链表优点:

    1. 按需申请空间,不用即释放空间,更合理的利用了内存空间
    2. 头部中间插入数据,不需要挪动数据,不存在空间浪费

    链表缺点:

    1. 每个数据都要存一个指针去链接后面的数据节点,存储密度小
    2. 不支持随机访问

    单纯单链表增删查改的意义不大,它更多的是去做更复杂结构的子结构,链表存储数据还得看双向链表

    单链表不适合在pos前插入数据,它适合在pos后插入数据

    案例详解

    案例通过c语言来创建一个无头单向链表

    目录

    案例详解

    数据节点的结构

    接口函数的声明

    创建新节点

    打印链表

    尾插

    头插

    尾删

    头删

    查找

    在pos位置前插入一个节点

    在pos位置后插入一个节点

    删除当前位置

    删除pos后一个位置

    销毁链表

    完整文件:

    SList.h

    SList.cpp

    Test.cpp


    数据节点的结构

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

    接口函数的声明

    1. //接口函数
    2. void SListPrint(SLTNode* phead);//打印链表
    3. void SListPushBack(SLTNode** pphead, SLTDataType x);//尾插
    4. void SListPushFront(SLTNode** pphead, SLTDataType x);//头插
    5. void SListPopBack(SLTNode** pphead);//尾删
    6. void SListPopFront(SLTNode** pphead);//头删
    7. //查找
    8. SLTNode* SListFind(SLTNode* pphead, SLTDataType x);
    9. //在pos位置前插入一个节点
    10. void SListInsert(SLTNode** phead, SLTNode* pos, SLTDataType x);
    11. //在pos位置后插入一个节点
    12. void SListInsertAfter(SLTNode* pos, SLTDataType x);
    13. //删除当前位置
    14. void SListErase(SLTNode** pphead, SLTNode* pos);
    15. //删除pos后一个位置
    16. void SListEraseAfter(SLTNode* pos);
    17. //销毁链表
    18. void SListDestroy(SLTNode** pphead);

    创建新节点

    为了提高创建新节点的代码的复用率

    1. //创建新节点
    2. SLTNode* BuyNode(SLTDataType x) {
    3. SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    4. if (newnode == NULL) {
    5. printf("malloc fail\n");
    6. exit(-1);
    7. }
    8. newnode->data = x;
    9. newnode->next = NULL;
    10. return newnode;
    11. }

    打印链表

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

    尾插

    1. //尾插
    2. void SListPushBack(SLTNode** pphead, SLTDataType x)
    3. {
    4. SLTNode* newnode = BuyNode(x);
    5. newnode->next = *pphead;
    6. *pphead = newnode;
    7. }

    头插

    需要考虑链表是否为空链表

    1. //头插
    2. void SListPushFront(SLTNode** pphead, SLTDataType x)
    3. {
    4. SLTNode* newnode = BuyNode(x);
    5. if (*pphead == NULL) {
    6. *pphead = newnode;
    7. }
    8. else {
    9. SLTNode* tail = *pphead;
    10. //找到尾结点
    11. while (tail->next != NULL) {
    12. tail = tail->next;
    13. }
    14. tail->next = newnode;
    15. }
    16. }

    尾删

    需要考虑三种情况:1、链表为空     2、链表只有一个节点     3、链表有两个及以上节点

    1. //尾删
    2. void SListPopBack(SLTNode** pphead)
    3. {
    4. //此表为空
    5. if (*pphead == NULL) {
    6. return;
    7. }
    8. //只有一个节点
    9. if (( * pphead)->next == NULL) {
    10. free(*pphead);
    11. *pphead = NULL;
    12. }
    13. else {
    14. //两个及两个以上节点
    15. //第一种方式
    16. SLTNode* prev = NULL;
    17. SLTNode* tail = *pphead;
    18. while (tail->next) {
    19. prev = tail;
    20. tail = tail->next;
    21. }
    22. free(tail);
    23. tail = NULL;
    24. prev->next = NULL;
    25. //第二种方式
    26. /* 少定义一个变量
    27. SLTNode* tail = *pphead;
    28. while (tail->next->next) {
    29. tail = tail->next;
    30. }
    31. free(tail->next);
    32. tail->next = NULL;
    33. */
    34. }
    35. }

    头删

    1. //头删
    2. void SListPopFront(SLTNode** pphead)
    3. {
    4. if (*pphead == NULL) {
    5. return;
    6. }
    7. SLTNode* next = (*pphead)->next;
    8. free(*pphead);
    9. *pphead = next;
    10. }

    查找

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

    在pos位置前插入一个节点

    前插会有一定的效率损耗,时间复杂度为O(n)

    1. //在pos位置前插入一个节点
    2. void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
    3. {
    4. SLTNode* newnode = BuyNode(x);
    5. if (*pphead == pos) {
    6. newnode->next = *pphead;
    7. *pphead = newnode;
    8. }
    9. else {
    10. SLTNode* posPrev = *pphead;
    11. while (posPrev->next != pos) {
    12. posPrev = posPrev->next;
    13. }
    14. posPrev->next = newnode;
    15. newnode->next = pos;
    16. }
    17. }

    在pos位置后插入一个节点

    后插效率比前插高,时间复杂度为O(1)

    1. //在pos位置后插入一个节点
    2. void SListInsertAfter(SLTNode* pos, SLTDataType x) {
    3. SLTNode* newnode = BuyNode(x);
    4. newnode->next = pos->next;
    5. pos->next = newnode;
    6. }

    删除当前位置

    1. //删除当前位置
    2. void SListErase(SLTNode** pphead, SLTNode* pos)
    3. {
    4. if (*pphead == pos)
    5. {
    6. *pphead = pos->next;
    7. free(pos);
    8. }
    9. else {
    10. SLTNode* prev = *pphead;
    11. while (prev->next != pos) {
    12. prev = prev->next;
    13. }
    14. prev->next = pos->next;
    15. free(pos);
    16. pos = NULL;
    17. }
    18. }

    删除pos后一个位置

    1. //删除pos后一个位置
    2. void SListEraseAfter(SLTNode* pos)
    3. {
    4. assert(pos->next);
    5. SLTNode* next = pos->next;
    6. pos->next = next->next;
    7. free(next);
    8. }

    销毁链表

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

    需注意释放内存后指针需要指NULL,不然就是典型的野指针问题

    在接口函数前可加入断言assert(pphead),如*phead=NULL,但是&phead永远不为空

    完整文件:

    SList.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. SLTDataType data;
    8. struct SListNode* next;
    9. }SLTNode;
    10. //接口函数
    11. void SListPrint(SLTNode* phead);//打印链表
    12. void SListPushBack(SLTNode** pphead, SLTDataType x);//尾插
    13. void SListPushFront(SLTNode** pphead, SLTDataType x);//头插
    14. void SListPopBack(SLTNode** pphead);//尾删
    15. void SListPopFront(SLTNode** pphead);//头删
    16. //查找
    17. SLTNode* SListFind(SLTNode* pphead, SLTDataType x);
    18. //在pos位置前插入一个节点
    19. void SListInsert(SLTNode** phead, SLTNode* pos, SLTDataType x);
    20. //在pos位置后插入一个节点
    21. void SListInsertAfter(SLTNode* pos, SLTDataType x);
    22. //删除当前位置
    23. void SListErase(SLTNode** pphead, SLTNode* pos);
    24. //删除pos后一个位置
    25. void SListEraseAfter(SLTNode* pos);
    26. //销毁链表
    27. void SListDestroy(SLTNode** pphead);

    SList.cpp

    1. #include "SList.h"
    2. //创建新节点
    3. SLTNode* BuyNode(SLTDataType x) {
    4. SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    5. if (newnode == NULL) {
    6. printf("malloc fail\n");
    7. exit(-1);
    8. }
    9. newnode->data = x;
    10. newnode->next = NULL;
    11. return newnode;
    12. }
    13. //打印链表
    14. void SListPrint(SLTNode* phead)
    15. {
    16. SLTNode* cur = phead;
    17. while (cur != NULL) {
    18. printf("%d ->", cur->data);
    19. cur = cur -> next;
    20. }
    21. printf("NULL\n");
    22. }
    23. //尾插
    24. void SListPushBack(SLTNode** pphead, SLTDataType x)
    25. {
    26. SLTNode* newnode = BuyNode(x);
    27. newnode->next = *pphead;
    28. *pphead = newnode;
    29. }
    30. //头插
    31. void SListPushFront(SLTNode** pphead, SLTDataType x)
    32. {
    33. SLTNode* newnode = BuyNode(x);
    34. if (*pphead == NULL) {
    35. *pphead = newnode;
    36. }
    37. else {
    38. SLTNode* tail = *pphead;
    39. //找到尾结点
    40. while (tail->next != NULL) {
    41. tail = tail->next;
    42. }
    43. tail->next = newnode;
    44. }
    45. }
    46. //尾删
    47. void SListPopBack(SLTNode** pphead)
    48. {
    49. //此表为空
    50. if (*pphead == NULL) {
    51. return;
    52. }
    53. //只有一个节点
    54. if (( * pphead)->next == NULL) {
    55. free(*pphead);
    56. *pphead = NULL;
    57. }
    58. else {
    59. //两个及两个以上节点
    60. //第一种方式
    61. SLTNode* prev = NULL;
    62. SLTNode* tail = *pphead;
    63. while (tail->next) {
    64. prev = tail;
    65. tail = tail->next;
    66. }
    67. free(tail);
    68. tail = NULL;
    69. prev->next = NULL;
    70. //第二种方式
    71. /* 少定义一个变量
    72. SLTNode* tail = *pphead;
    73. while (tail->next->next) {
    74. tail = tail->next;
    75. }
    76. free(tail->next);
    77. tail->next = NULL;
    78. */
    79. }
    80. }
    81. //头删
    82. void SListPopFront(SLTNode** pphead)
    83. {
    84. if (*pphead == NULL) {
    85. return;
    86. }
    87. SLTNode* next = (*pphead)->next;
    88. free(*pphead);
    89. *pphead = next;
    90. }
    91. //查找
    92. SLTNode* SListFind(SLTNode* phead, SLTDataType x)
    93. {
    94. SListNode* cur = phead;
    95. while (cur) {
    96. if (cur->data == x) {
    97. return cur;
    98. }
    99. else {
    100. cur = cur->next;
    101. }
    102. }
    103. return NULL;
    104. }
    105. //在pos位置前插入一个节点
    106. void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
    107. {
    108. SLTNode* newnode = BuyNode(x);
    109. if (*pphead == pos) {
    110. newnode->next = *pphead;
    111. *pphead = newnode;
    112. }
    113. else {
    114. SLTNode* posPrev = *pphead;
    115. while (posPrev->next != pos) {
    116. posPrev = posPrev->next;
    117. }
    118. posPrev->next = newnode;
    119. newnode->next = pos;
    120. }
    121. }
    122. //在pos位置后插入一个节点
    123. void SListInsertAfter(SLTNode* pos, SLTDataType x) {
    124. SLTNode* newnode = BuyNode(x);
    125. newnode->next = pos->next;
    126. pos->next = newnode;
    127. }
    128. //删除当前位置
    129. void SListErase(SLTNode** pphead, SLTNode* pos)
    130. {
    131. if (*pphead == pos)
    132. {
    133. *pphead = pos->next;
    134. free(pos);
    135. }
    136. else {
    137. SLTNode* prev = *pphead;
    138. while (prev->next != pos) {
    139. prev = prev->next;
    140. }
    141. prev->next = pos->next;
    142. free(pos);
    143. pos = NULL;
    144. }
    145. }
    146. //删除pos后一个位置
    147. void SListEraseAfter(SLTNode* pos)
    148. {
    149. assert(pos->next);
    150. SLTNode* next = pos->next;
    151. pos->next = next->next;
    152. free(next);
    153. }
    154. //销毁链表
    155. void SListDestroy(SLTNode** pphead)
    156. {
    157. assert(pphead);
    158. SLTNode* cur = *pphead;
    159. while (cur) {
    160. SLTNode* next = cur->next;
    161. free(cur);
    162. cur = next;
    163. }
    164. *pphead = NULL;
    165. }

    Test.cpp

    1. #include "SList.h"
    2. int main()
    3. {
    4. SListNode* plist = NULL;
    5. SListPushBack(&plist, 1);
    6. SListPushBack(&plist, 2);
    7. SListPushBack(&plist, 3);
    8. SListPushBack(&plist, 4);
    9. SListPrint(plist);
    10. SLTNode* pos = SListFind(plist,3);
    11. SListPrint(plist);
    12. SListInsert(&plist, pos, 333);
    13. SListPrint(plist);
    14. SListInsertAfter(pos, 33333333);
    15. SListPrint(plist);
    16. SListErase(&plist, pos);
    17. SListPrint(plist);
    18. pos = SListFind(plist, 333);
    19. SListEraseAfter(pos);
    20. SListPrint(plist);
    21. SListDestroy(&plist);
    22. SListPrint(plist);
    23. }

  • 相关阅读:
    由面试题“Redis是否为单线程”引发的思考【文末送书-23】
    127.(前端)富文本的使用——vue使用超文本
    SpringBoot项目打包与运行
    Axios请求封装
    常用的英文缩写总结,专业术语
    Redis数据结构之——ziplist
    JSON 教程
    基于Redis自增实现全局ID生成器(详解)
    观察者模式(Observer Pattern)
    thinkphp withJoin 模式下field 无效
  • 原文地址:https://blog.csdn.net/Thewei666/article/details/127742237