• 【数据结构】详解链表(一)——单链表(动图讲解)


    作者:一个喜欢猫咪的的程序员

    专栏:《数据结构》

    喜欢的话:世间因为少年的挺身而出,而更加瑰丽。                                  ——《人民日报》


    目录

    前言:

    单链表的结构和形成:

    逻辑结构:

     物理结构(从计算机角度):

    BuySLTNode(创建结构体): 

    CreateSList部分(n个结构体形成链表):

     PrintSList(打印单链表):

     单链表的功能实现(增删查改):

    SLTPushBack尾插部分:

    当plist不为NULL时:

    当plist为NULL时:

    SLTPopBack(尾删部分):

    SLTPushFront(头插部分):

     SLTPopFront(头删部分):

    SLTFide(查找部分):

    SLTInsertAfter(在pos位置之后插入):

     SLTInsertFront(在pos位置之前插入):

    SLTEraseAfter(删除pos之后的位置): 

     SLTErase(删除pos位置):

     SLTDestory(释放空间):

    Slist.h头文件引用和结构体定义: 

    Slist.c各种功能函数实现:

    Test.c测试文件: 


    前言:

    单链表是一种链式存取的 数据结构 ,用一组地址任意的 存储单元 存放线性表中的数据元素。 链表中的数据是以结点来表示的,每个结点的构成:元素 ( 数据元素 的映象) + 指针 (指示后继元素 存储 位置),元素就是存储数据的存储单元,指针就是连接每个结点的 地址 数据。 链表中的数据是以结点来表示的,每个结点的构成:元素 ( 数据元素 的映象) + 指针 (指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。 以“结点的序列”表示线性表称作 线性链表 (单链表),单链表是链式存取的结构。 链接方式存储的线性表简称为链表(Linked List)。 ② 链表中结点的逻辑次序和物理次序不一定相同。


    单链表的结构和形成:

    将一个结构体包含两个部分,一个是数据,一个是与本身结构体同类型的结构体指针

    1. typedef int SLDataType;
    2. ct SListNode {//单链表
    3. SLDataType data;//数据
    4. struct SListNode* next;//指向下一个与本结构体相同的结构体变量的指针
    5. }SLTNode;

    这样就可以在结构体指针找到下一个结构体的位置,第一个指针找到了第二个结构体,第二个结构体指针找到第三个结构体,以此类推....这样就形成了一个链表。

    逻辑结构

     但计算机实际上并没有指向的箭头啊,我们可以通过指针指向下一个结构体的地址来实现!

     物理结构(从计算机角度):

    BuySLTNode(创建结构体): 

    我们是动态开辟malloc结构体,malloc函数可能会异地扩、本地扩或者NULL,因此我们需要判断malloc返回值是否为空!以及我们需要把地址传回去,防止异地扩后,地址不同

    对于malloc函数不熟悉的同学可以看看我的另外一篇博客:对动态内存开辟有详细解释http://t.csdn.cn/IkDCFhttp://t.csdn.cn/IkDCF

    言归正传。我们来看一下这个函数如何实现:

    1. SLTNode* BuySLTNode(SLDataType x)
    2. {//创建结构体
    3. SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    4. if (newnode == NULL)
    5. {
    6. perror("malloc fail");
    7. exit(-1);
    8. }
    9. newnode->data = x;
    10. newnode->next = NULL;
    11. return newnode;
    12. }

    CreateSList部分(n个结构体形成链表):

    我们这种情况是通过写4个语句来将这4个结构体穿起来,那如果我们要n个结构体形成链表呢?

    我们结合一个图,来想想怎么把图中代码的能力实现,并实现n个呢?

     n个结构体相连,所以肯定是for循环,利用BuyTNode创建动态结构体,我们再将下一个结构体的地址传给结构体的next指针以此类推.....

    1. SLTNode* CreateSList(int n)
    2. {//将n个结构体形成链表
    3. SLTNode* phead = NULL;
    4. SLTNode* plist = NULL;
    5. for (int i = 0; i < n; i++)
    6. {
    7. SLTNode* newnode = BuySLTNode(i);
    8. if (plist == NULL)
    9. {
    10. phead = plist = newnode;
    11. }
    12. else
    13. {
    14. plist->next = newnode;
    15. plist = newnode;
    16. }
    17. }
    18. return phead;
    19. }
    20. v

    因为使用BuySLTNode创建结构体时,next都为NULL。

    plist.next=newnode来形成链表,我们这边有一个矛盾,当第一个结构体时,并没有第二个来赋值,我们可以这样:plist=newnode,这样的时候当newnode为第二个结构体的地址的时候就不为NULL,就会将第二个结构体赋值给第一个结构体的next指针将之前的覆盖。


     PrintSList(打印单链表):

    我们利用一个while循环遍历一遍打印就好。

    1. void* PrintSList(SLTNode* phead)
    2. {//打印链表
    3. SLTNode* cur = phead;
    4. while (cur!= NULL)
    5. //如果这里是cur->next != NULL的话,会少一个值,因为cur->next==NULL时,cur->data有值的,它的下一个结构体才没有值
    6. {
    7. printf("[%d|%p]->", cur->data,cur->next);
    8. cur = cur->next;//这里如果想要直接cur->next=...的话有点困难
    9. }
    10. printf("NULL");
    11. }

    如果这里是cur->next != NULL的话,会少一个值,因为cur->next==NULL时,cur->data有值的,它的下一个结构体才没有值


     单链表的功能实现(增删查改):

    SLTPushBack尾插部分:

    当plist不为NULL时:

    我们尾插要先找尾部,即NULL的地方。然后将newnode插到null后面就好了。

    我们来看下面这个尾插是否有错?

     可能很多人觉得这样没有问题,但实际上是错的!!!

    • tail为NULL和tail->next为NULL对于循环条件来说是不一样的。
    • tail为NULL代表没有这个结构体,就下图4个结构体为例:当tail指向第4个结构体时,循环不会结束,tail下一个会指向NULL。当循环停止的时候,tail已经指向了NULL,而第四个结构体的next还是指向空,即使我们插进去一个结构体,也会在第四个结构体的时候next到NULL,相当于没有插入!

    • tail->next为NULL代表没有下一个结构体了,这样tail->next=newnode就会将之前的尾部结构体和新加入的结构体连接起来形成链表!

    当循环条件为tail.next!=NULL时:

    1. void SLTPushBack(SLTNode* phead, SLDataType x)
    2. {//尾插
    3. //assert(phead);//这里不需要断言,空链表也可以尾插
    4. SLTNode* newnode = BuySLTNode(x);
    5. SLTNode* tail = phead;
    6. while (tail->next)
    7. {
    8. tail = tail->next;
    9. }
    10. tail->next = newnode;
    11. }

    当plist为NULL时:

    这个是我在plist为NULL时运行结果的截图:

     这里错误是由指针传参导致的!

    你一定很疑惑把我明明把指针传过去,传地址不是可以改变原来的值吗?没有错啊.

    我们先来复习一下:

    • 你的老师一定跟你说函数在传值调用时,形参不可以改变实参
    • 传址调用才可以改变实参!

    这是没有错的,但是它是相对而言的。 

    • 指针本质是一个地址,我们所说的指针的值是一个地址,而不是这个地址指向的值。
    •  NULL为空指针,NULL=0x0000000000(这是一个地址)。

    我们要改变NULL这个指针的时候,注意:我们这里说要改变这个指针是指我们要改变这个地址(0x00000000),而不是这个地址的值。

    那我们这里的 SLTNode* phead是不是相当于上图int a。

     因此我们要改变NULL这个指针,我们需要传SLTNode**pphead进去,那我们*pphead就可以改变NULL的值了!

    1. void SLTPushBack(SLTNode** pphead, SLDataType x)
    2. {//尾插
    3. //assert(phead);//这里不需要断言,空链表也可以尾插
    4. SLTNode* newnode = BuySLTNode(x);
    5. if (*pphead == NULL)
    6. {
    7. *pphead = newnode;
    8. }
    9. else {
    10. SLTNode* tail = *pphead;
    11. while (tail->next)
    12. {
    13. tail = tail->next;
    14. }
    15. tail->next = newnode;
    16. }
    17. }

    SLTPopBack(尾删部分):

    找到尾部,把最后一个结构体删掉,再把前一个结构体的next置为NULL

    1. void SLTPopBack(SLTNode** pphead)
    2. {//尾删
    3. assert(*pphead);
    4. if ((*pphead)->next == NULL)
    5. {
    6. free(*pphead);
    7. *pphead = NULL;
    8. }
    9. else
    10. {
    11. SLTNode* tail = *pphead;
    12. while (tail->next->next)
    13. {
    14. tail = tail->next;
    15. }
    16. free(tail->next);
    17. tail->next = NULL;
    18. }
    19. }

    SLTPushFront(头插部分):

    1. void SLTPushFront(SLTNode** pphead, SLDataType x)
    2. {//头插
    3. SLTNode* newnode = BuySLTNode(x);
    4. newnode->next = *pphead;
    5. *pphead = newnode;
    6. }

     SLTPopFront(头删部分):

    1. void SLTPopFront(SLTNode** pphead)
    2. {//头删
    3. assert(*pphead);
    4. SLTNode* next = (*pphead)->next;
    5. free(*pphead);
    6. *pphead = next;
    7. }

    SLTFide(查找部分):

    1. SLTNode* SLTFide(SLTNode* phead, SLDataType 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. }

    SLTInsertAfter(在pos位置之后插入):

    假设pos在d3的位置:

    1. void SLTInsertAfter(SLTNode* pos, SLDataType x)
    2. {//在pos位置之后插入
    3. assert(pos);
    4. SLTNode* newnode = BuySLTNode(x);
    5. newnode->next = pos->next;
    6. pos->next = newnode;
    7. }

    为什么同样是插入数据,头插尾插要传二级指针,而这个函数要传一级指针呢?

    在pos位置插入的话,首先这个pos得不为NULL。

    而NULL空链表时,pos的值就为NULL,因此这里不需要改变指针的值,所以一级指针就够了! 


     SLTInsertFront(在pos位置之前插入):

    分为两种情况:

    • pos为链表第一个,那就相当于头插;
    • 除去第一个以外的位置:

    1. void SLTInsertFront(SLTNode** pphead, SLTNode* pos, SLDataType x)
    2. {//在pos之前插入
    3. assert(pos);
    4. SLTNode* newnode = BuySLTNode(x);
    5. if(*pphead == pos)
    6. {
    7. SLTPushFront(pphead, x);
    8. }
    9. else
    10. {
    11. SLTNode* prev = *pphead;
    12. while (prev->next != pos)
    13. {
    14. prev = prev->next;
    15. }
    16. prev->next = newnode;
    17. newnode->next = pos;
    18. }
    19. }


    SLTEraseAfter(删除pos之后的位置): 

    找pos位置的下一个位置将它赋给一个指针nextNode,将pos的next连接到nextNode的下一个结构体的位置,这样就跳过这个指针了,还不要忘了释放这个被跳过的空间!!!

    1. void SLTEraseAfter(SLTNode* pos)
    2. {//删除pos之后的位置
    3. assert(pos);
    4. if (pos->next == NULL)
    5. {
    6. return;
    7. }
    8. else
    9. {
    10. SLTNode* nextNode = pos->next;
    11. pos->next = nextNode->next;
    12. free(nextNode);
    13. }
    14. }

     SLTErase(删除pos位置):

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

     SLTDestory(释放空间):

    链表并不是连续的,只是一个又一个结构体跳过结构体指针链接起来,一次我们要一个一个释放!

    1. void SLTDestory(SLTNode** pphead)
    2. {
    3. SLTNode* cur = *pphead;
    4. while (cur)
    5. {
    6. SLTNode* next = cur->next;
    7. free(cur);
    8. cur = next;
    9. }
    10. *pphead = NULL;
    11. }

    Slist.h头文件引用和结构体定义: 

    1. #pragma once
    2. #include<stdio.h>
    3. #include<stdlib.h>
    4. #include<assert.h>
    5. typedef int SLDataType;
    6. //typedef struct SListNode {//单链表
    7. // SLDataType data;//数据
    8. // struct SListNode* next;//指向下一个与本结构体相同的结构体变量的指针
    9. //
    10. // //可以这么写吗?
    11. // //SLTNode* next;//答案是不可以的,SLTNode要在下一行之后才可以使用
    12. //}SLTNode;
    13. typedef struct SListNode {//单链表
    14. SLDataType data;//数据
    15. struct SListNode* next;//指向下一个与本结构体相同的结构体变量的指针
    16. }SLTNode;
    17. SLTNode* BuySLTNode(SLDataType x);//创建结构体
    18. SLTNode* CreateSList(int n);//将n个结构体形成链表
    19. void PrintSList(SLTNode* phead);//打印链表
    20. void SLTPushBack(SLTNode** pphead, SLDataType x);//尾插
    21. void SLTPopBack(SLTNode** pphead);//尾删
    22. void SLTPushFront(SLTNode** pphead, SLDataType x);//头插
    23. void SLTPopFront(SLTNode** pphead);//头删
    24. SLTNode* SLTFide(SLTNode* phead, SLDataType x);//查找
    25. void SLTInsertAfter(SLTNode* pos, SLDataType x);//在pos位置之后插入
    26. void SLTInsertFront(SLTNode** pphead,SLTNode* pos,SLDataType x);//在pos位置之前插入
    27. void SLTEraseAfter(SLTNode* pos);//在删除pos之后的位置
    28. void SLTErase(SLTNode**pphead,SLTNode* pos);//删除pos位置
    29. void SLTDestory(SLTNode** pphead);

    Slist.c各种功能函数实现:

    1. #include"Slist.h"
    2. SLTNode* BuySLTNode(SLDataType x)
    3. {//创建结构体
    4. SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    5. if (newnode == NULL)
    6. {
    7. perror("malloc fail");
    8. exit(-1);
    9. }
    10. newnode->data = x;
    11. newnode->next = NULL;
    12. return newnode;
    13. }
    14. SLTNode* CreateSList(int n)
    15. {//将n个结构体形成链表
    16. SLTNode* phead = NULL;
    17. SLTNode* plist = NULL;
    18. for (int i = 0; i < n; i++)
    19. {
    20. SLTNode* newnode = BuySLTNode(i);
    21. if (plist == NULL)
    22. {
    23. phead = plist = newnode;
    24. }
    25. else
    26. {
    27. plist->next = newnode;
    28. plist = newnode;
    29. }
    30. }
    31. return phead;
    32. }
    33. void PrintSList(SLTNode* phead)
    34. {//打印链表
    35. SLTNode* cur = phead;
    36. while (cur!= NULL)
    37. //如果这里是cur->next != NULL的话,会少一个值,因为cur->next==NULL时,cur->data有值的,它的下一个结构体才没有值
    38. {
    39. printf("%d->", cur->data);
    40. /*printf("[%d|%p]->", cur->data, cur->next);*/
    41. cur = cur->next;//这里如果想要直接cur->next=...的话有点困难
    42. }
    43. printf("NULL\n");
    44. }
    45. void SLTPushBack(SLTNode** pphead, SLDataType x)
    46. {//尾插
    47. //assert(phead);//这里不需要断言,空链表也可以尾插
    48. SLTNode* newnode = BuySLTNode(x);
    49. if (*pphead == NULL)
    50. {
    51. *pphead = newnode;
    52. }
    53. else {
    54. SLTNode* tail = *pphead;
    55. while (tail->next)
    56. {
    57. tail = tail->next;
    58. }
    59. tail->next = newnode;
    60. }
    61. }
    62. void SLTPopBack(SLTNode** pphead)
    63. {//尾删
    64. assert(*pphead);
    65. if ((*pphead)->next == NULL)
    66. {
    67. free(*pphead);
    68. *pphead = NULL;
    69. }
    70. else
    71. {
    72. SLTNode* tail = *pphead;
    73. while (tail->next->next)
    74. {
    75. tail = tail->next;
    76. }
    77. free(tail->next);
    78. tail->next = NULL;
    79. }
    80. }
    81. void SLTPushFront(SLTNode** pphead, SLDataType x)
    82. {//头插
    83. SLTNode* newnode = BuySLTNode(x);
    84. newnode->next = *pphead;
    85. *pphead = newnode;
    86. }
    87. void SLTPopFront(SLTNode** pphead)
    88. {//头删
    89. assert(*pphead);
    90. SLTNode* next = (*pphead)->next;
    91. free(*pphead);
    92. *pphead = next;
    93. }
    94. SLTNode* SLTFide(SLTNode* phead, SLDataType x)
    95. {//查找
    96. SLTNode* cur = phead;
    97. while (cur)
    98. {
    99. if (cur->data == x)
    100. {
    101. return cur;
    102. }
    103. cur = cur->next;
    104. }
    105. return NULL;
    106. }
    107. void SLTInsertAfter(SLTNode* pos, SLDataType x)
    108. {//在pos位置之后插入
    109. assert(pos);
    110. SLTNode* newnode = BuySLTNode(x);
    111. newnode->next = pos->next;
    112. pos->next = newnode;
    113. }
    114. void SLTInsertFront(SLTNode** pphead, SLTNode* pos, SLDataType x)
    115. {//在pos之前插入
    116. assert(pos);
    117. SLTNode* newnode = BuySLTNode(x);
    118. if(*pphead == pos)
    119. {
    120. SLTPushFront(pphead, x);
    121. }
    122. else
    123. {
    124. SLTNode* prev = *pphead;
    125. while (prev->next != pos)
    126. {
    127. prev = prev->next;
    128. }
    129. prev->next = newnode;
    130. newnode->next = pos;
    131. }
    132. }
    133. void SLTEraseAfter(SLTNode* pos)
    134. {//删除pos之后的位置
    135. assert(pos);
    136. if (pos->next == NULL)
    137. {
    138. return;
    139. }
    140. else
    141. {
    142. SLTNode* nextNode = pos->next;
    143. pos->next = nextNode->next;
    144. free(nextNode);
    145. }
    146. }
    147. void SLTErase(SLTNode** pphead,SLTNode* pos)
    148. {//删除pos位置
    149. assert(pos);
    150. if (pos = *pphead)
    151. {
    152. SLTEraseAfter(pos);
    153. }
    154. else
    155. {
    156. SLTNode* prev = *pphead;
    157. while (prev->next != pos)
    158. {
    159. prev = prev->next;
    160. }
    161. prev->next = pos->next;
    162. free(pos);
    163. }
    164. }
    165. void SLTDestory(SLTNode** pphead)
    166. {
    167. SLTNode* cur = *pphead;
    168. while (cur)
    169. {
    170. SLTNode* next = cur->next;
    171. free(cur);
    172. cur = next;
    173. }
    174. *pphead = NULL;
    175. }

    Test.c测试文件: 

    1. #include"Slist.h"
    2. //void test1()
    3. //{
    4. // //第一种写法:
    5. // SLTNode* n1 = malloc();
    6. // SLTNode* n2 = malloc();
    7. // n1->next = n2;
    8. // //这里的n1是存放在堆区的,出了这个函数不会被销毁!
    9. //
    10. // //第二种写法:
    11. // //SLTNode n2;
    12. // //SLTNode n3;
    13. // //n2.next = &n3;
    14. // //n2这里是存放函数栈帧里面的,出了函数就会被销毁,因此不能这样写!
    15. //}
    16. void test2()
    17. {
    18. //SLTNode* n1 = BuySLTNode(1);
    19. //SLTNode* n2 = BuySLTNode(2);
    20. //SLTNode* n3 = BuySLTNode(3);
    21. //SLTNode* n4 = BuySLTNode(4);
    22. //n1->next = n2;
    23. //n2->next = n3;
    24. //n3->next = n4;
    25. //n4->next = NULL;
    26. SLTNode*plist=CreateSList(10);
    27. PrintSList(plist);
    28. printf("\n");
    29. SLTPushBack(plist, 100);
    30. PrintSList(plist);
    31. }
    32. void test3()
    33. {
    34. SLTNode* plist = NULL;
    35. SLTPushBack(&plist, 100);
    36. SLTPushBack(&plist, 200);
    37. SLTPushBack(&plist,300);
    38. PrintSList(plist);
    39. SLTPopBack(&plist);
    40. PrintSList(plist);
    41. /*SLTPopBack(&plist);
    42. PrintSList(plist);
    43. SLTPopBack(&plist);
    44. PrintSList(plist);*/
    45. SLTPushFront(&plist, 1);
    46. PrintSList(plist);
    47. SLTPopFront(&plist);
    48. PrintSList(plist);
    49. SLTPopFront(&plist);
    50. PrintSList(plist);
    51. SLTPopFront(&plist);
    52. PrintSList(plist);
    53. }
    54. void test4()
    55. {
    56. SLTNode* plist = NULL;
    57. SLTNode* pos1 = SLTFide(plist, 200);
    58. if (pos1 == NULL)
    59. {
    60. printf("找不到\n");
    61. }
    62. else
    63. {
    64. printf("找到了\n");
    65. }
    66. SLTPushBack(&plist, 100);
    67. SLTPushBack(&plist, 200);
    68. SLTPushBack(&plist, 300);
    69. PrintSList(plist);
    70. SLTNode*pos=SLTFide(plist, 200);
    71. //SLTInsertAfter(pos, 10);
    72. //PrintSList(plist);
    73. //SLTInsertFront(&plist, pos, 1);
    74. //PrintSList(plist);
    75. SLTErase(&plist,pos);
    76. PrintSList(plist);
    77. SLTDestory(&plist);
    78. //if (pos == NULL)
    79. //{
    80. // printf("找不到\n");
    81. //}
    82. //else
    83. //{
    84. // printf("找到了\n");
    85. //}
    86. }
    87. int main()
    88. {
    89. test4();
    90. return 0;
    91. }

  • 相关阅读:
    一个越南程序员的阿里巴巴之旅
    表单追加数据 - 工作经历
    map的常用用法详解(新手入门!!!)
    Java 变得越来越像 Rust?
    加列法计算lower unit matrix inversion
    MySQL主/从-主/主集群安装部署
    Java基础(十九):集合框架
    Java进阶篇--并发容器之ThreadLocal
    canvas基础简单易懂教程(完结,多图)
    java-php-python-ssm校园闲置物品交换平台系统计算机毕业设计
  • 原文地址:https://blog.csdn.net/m0_69061857/article/details/127745985