当前位置:   article > 正文

【数据结构和算法】5.超详解析,带你手撕单向链表(图文解析,附带源码)

【数据结构和算法】5.超详解析,带你手撕单向链表(图文解析,附带源码)

欢迎来sobercq的博客喔,本期系列为【数据结构和算法】5.超详解析,带你手撕单向链表(图文解析,附带源码),带大家理解单向链表在内存中的分布,以及链表的实现,最后还会有源码分享,感谢观看,支持的可以给个赞哇。

前言

在上一篇我们知道了线性表中顺序表的问题,本期我们将讨论线性表的另一种结构,链表,由于它不要求像顺序表一样开辟连续的物理空间,并且要求逻辑上相邻的元素在物理位置上也顺序存储,也因此链表不需要移动大量的数据,但同时也失去了随机下标访问的优势。

一、链表的概念及结构

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

结构:

对链表中的数据元素来说,除了存储其本身的信息外,还需存储一个指示其后继的信息(地址),这两部分信息组成数据元素的存储映像,称为节点或者结点。它在内存中有两个域,其中存储数据元素本身的信息的域叫做数据域,存储后继位置地址的域称为指针域。指针域中存储的信息就叫做指针或链。n个结点链结成链表,因为链表中的每个结点只包含一个指针域,故又称为线性链表或者单链表

二、单向链表的结构

定义链表结构,我们使用一个头指针phead指向链表的起始位置,在一个结点中的指针域,也就是next,让其指向下一个结点的起始位置,在单向链表的末端,也就是最后一个结点,让它的next指向NULL,这样一个单向链表的结构定义就完成了。

 链表的结构如下图所示:

  1. typedef int SLNDataType;
  2. typedef struct SListNode
  3. {
  4. SLNDataType val;
  5. struct SListNode* next;
  6. }SLNode;

单向链表的存取必须从头指针开始进行(头指针指示链表中的第一个结点的存储位置)同时,最后一个结点的指针为“空”(NULL)(因为最后一个数据元素没有直接后继)。

三、单向链表功能实现

(1).单向链表的打印

在打印单向链表,我们通过指针next来找到下一个结点,然后打印这一个结点的值。但是注意一点,我们是不碰原先链表中的next的,所以在寻找链表的时候,需要定义一个指针cur来遍历链表,当cur指向到链表末尾的时候,这时候cur为NULL,也就意味着指针遍历结束。

单向链表逻辑结构上的打印:(逻辑结构和物理结构看第五大点)

  1. //打印单向链表
  2. void SLTPrint(SLNode* phead)
  3. {
  4. SLNode* cur = phead;
  5. while (cur != NULL)
  6. {
  7. printf("%d ", cur->val);
  8. cur = cur->next;
  9. }
  10. }

(2).单向链表的尾插 

为了插入数据元素x,我们首先要找到单向链表的“尾巴”,其次要开辟一个结点,其数据为x,然后插入单向链表中。并且要修改原先单向链表上一个结点的指针,使其指向新开辟出来的val为x的结点,让val为x的结点的指针next为"空"(NULL),这样就完成了尾插的操作。

找单向链表的尾巴:

我们定义一个tail来存储头部指针phead,接着去遍历单向链表。

找尾中可能出现的问题 

请看下列两个代码,并思考有何区别?

  1. SLNode* tail = phead;
  2. //找尾
  3. while (tail->next != NULL)
  4. {
  5. tail = tail->next;
  6. }
  1. SLNode* tail = phead;
  2. //找尾
  3. while (tail != NULL)
  4. {
  5. tail = tail->next;
  6. }

 在找尾部的时候,我们需要判断的是tail->next后的指针是否为空指针,在第二个代码中,其找尾部的最后结果通常是把NULL赋值给tail,而不是我们想要的把新创建的结点地址给到tail。因此需要提前判断tail的下一个结点是否为空指针。

逻辑结构如下图所示:

创建新的结点 :

我们用newnode来表示新结点,注意newnode不能在尾插当中直接创建(因为是局部变量的关系,局部变量出了作用域就销毁了),在函数外,我们创建一个新函数,并且通过创建内存空间来创建一个新结点。

  1. //创建一个新结点
  2. SLNode* CreateNode(SLNDataType x)
  3. {
  4. SLNode* newNode = (SLNode*)malloc(sizeof(SLNode));
  5. if (newNode == NULL)
  6. {
  7. perror("malloc fail");
  8. exit(-1);
  9. }
  10. newNode->val = x;
  11. newNode->next = NULL;
  12. return newNode;
  13. }

 插入结点:

  1. //尾插
  2. void SLTPushBack(SLNode* phead, SLNDataType x)
  3. {
  4. //找尾
  5. SLNode* tail = phead;
  6. while (tail->next != NULL)
  7. {
  8. tail = tail->next;
  9. }
  10. //创建新结点
  11. SLNode* newnode = CreateNode(x);
  12. //插入结点
  13. tail->next = newnode;
  14. }

如果链表开始为空怎么办?

我们需要先判断链表的状态,那么单向链表为空的时候,其实就是phead为空。

  1. //判断链表是否为空
  2. if (phead == NULL)
  3. {
  4. phead = newnode;
  5. }

最后梳理后,我们在代码中要先创建结点,然后判断链表状态,最后是插入。

  1. //尾插
  2. void SLTPushBack(SLNode* phead, SLNDataType x)
  3. {
  4. //创建新结点
  5. SLNode* newnode = CreateNode(x);
  6. //判断链表是否为空
  7. if (phead == NULL)
  8. {
  9. phead = newnode;
  10. }
  11. else
  12. {
  13. //找尾
  14. SLNode* tail = phead;
  15. while (tail->next != NULL)
  16. {
  17. tail = tail->next;
  18. }
  19. //插入结点
  20. tail->next = newnode;
  21. }
  22. }

尾插测试 

  1. int main()
  2. {
  3. SLNode* plist = NULL;
  4. SLTPushBack(&plist, 1);
  5. SLTPushBack(&plist, 2);
  6. SLTPushBack(&plist, 3);
  7. SLTPushBack(&plist, 4);
  8. SLTPrint(plist);
  9. return 0;
  10. }

我们在测试的时候发现问题,屏幕居然什么也没打印,回顾后发现,其实也就是当我传plist的时候,本意是想通过phead操作plist,但我们只拷贝了plist而并没有对plist进行操作,所以对原先一级指针phead要进行部分调整(尾插和头插的SLNode* phead就要改成SLNode** pphead)

 改完后的代码如下:

  1. int main()
  2. {
  3. SLNode* plist = NULL;
  4. SLTPushBack(&plist, 1);
  5. SLTPushBack(&plist, 2);
  6. SLTPushBack(&plist, 3);
  7. SLTPushBack(&plist, 4);
  8. SLTPrint(plist);
  9. return 0;
  10. }
  11. //尾插
  12. void SLTPushBack(SLNode** pphead, SLNDataType x)
  13. {
  14. //创建新结点
  15. SLNode* newnode = CreateNode(x);
  16. //判断链表是否为空
  17. if (*pphead == NULL)
  18. {
  19. *pphead = newnode;
  20. }
  21. else
  22. {
  23. //找尾
  24. SLNode* tail = *pphead;
  25. while (tail->next != NULL)
  26. {
  27. tail = tail->next;
  28. }
  29. //插入结点
  30. tail->next = newnode;
  31. }
  32. }
  33. //头插
  34. void SLTPushFront(SLNode** pphead, SLNDataType x)
  35. {
  36. ;
  37. }
  38. void SLTPushBack(SLNode** pphead, SLNDataType x);
  39. //尾插
  40. void SLTPushFront(SLNode** pphead, SLNDataType x);
  41. //头插

测试效果如下图所示:

至此,尾插的功能就完成了。

(3) .单向链表的头插

为了插入数据元素x,我们首先要创建一个结点,其数据为x,然后将phead指向新结点。并且要修改结点,使其指向原先单向链表的第一个val的结点。这样就完成了头插的操作。

  1. //头插
  2. void SLTPushFront(SLNode** pphead, SLNDataType x)
  3. {
  4. SLNode* newnode = CreateNode(x);
  5. newnode->next = *pphead;
  6. *pphead = newnode;
  7. }

 头插测试

  1. int main()
  2. {
  3. SLNode* plist = NULL;
  4. //尾插测试
  5. SLTPushBack(&plist, 1);
  6. SLTPushBack(&plist, 2);
  7. SLTPushBack(&plist, 3);
  8. SLTPushBack(&plist, 4);
  9. //头插测试
  10. SLTPushFront(&plist, 5);
  11. SLTPushFront(&plist, 6);
  12. SLTPushFront(&plist, 7);
  13. SLTPushFront(&plist, 8);
  14. SLTPrint(plist);
  15. return 0;
  16. }

可以看到如下结果: 

 

(4).单向链表的尾删

 为在单向链表中删除结点的时候,我们仅仅只需要修改上一个结点的指针域即可,而不需要对数据进行操作。尾删仍然需要找到尾巴,并且要判断链表是否只有一个结点。

 我们要对一个结点和多个结点的情况分类讨论。当我们只有一个结点的时候。在采取同样对多节点的时候会导致prev的指向。在删除的时候,我们还是需要对指针进行检查。

这里就不展开详细讲解了,代码如下所示: 

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

尾删的另外一种写法,我们知道tail用来表示一结点的起始位置,其中的next是下一个结点,而我们在这个前面的代码中,要找到tail的上一个结点,也就是要间隔一个结点判断是否为链表末端所以可以用如下代码:

  1. void SLTPopBack(SLNode** pphead)
  2. {
  3. assert(*pphead);
  4. SLNode* tail = *pphead;
  5. while (tail->next->next != NULL)
  6. {
  7. tail = tail->next;
  8. }
  9. free(tail->next);
  10. tail->next = NULL;
  11. }

 结果如下所示:

(5).单向链表的头删

在单向链表中删除结点的时候,从头部开始,我们需要修改下一个结点的指针域,让其赋值给pphead,这样就完成了头删的操作。

plist是我们传入的指针,*pphead是plist的二级指针。

操作过程逻辑结构如下图所示:

对第一个结点free了,然后让*pphead指向下一个结点。 

  1. void SLTPopFront(SLNode** pphead)
  2. {
  3. assert(*pphead);
  4. SLNode* tmp = (*pphead)->next;
  5. free(*pphead);
  6. *pphead = tmp;
  7. }

删除测试 

  1. //删除测试
  2. void Test2()
  3. {
  4. SLNode* plist = NULL;
  5. //尾部插入四个数据
  6. SLTPushBack(&plist, 1);
  7. SLTPushBack(&plist, 2);
  8. SLTPushBack(&plist, 3);
  9. SLTPushBack(&plist, 4);
  10. //尾删测试
  11. SLTPopBack(&plist);
  12. SLTPrint(plist);
  13. //尾删测试
  14. SLTPopBack(&plist);
  15. SLTPrint(plist);
  16. //头删测试
  17. SLTPopFront(&plist);
  18. SLTPrint(plist);
  19. }

结果如下图所示: 

(6).单向链表的查找 

用cur指针遍历链表,比对每个结点的值,如果值相等,则返回cur,如果cur遍历到NULL,也就是链表末尾,则返回NULL

  1. //链表查找
  2. SLNode* SLTFind(SLNode* phead, SLNDataType x)
  3. {
  4. SLNode* cur = phead;
  5. while (cur)
  6. {
  7. if (cur->val == x)
  8. {
  9. return cur;
  10. }
  11. else
  12. {
  13. cur = cur->next;
  14. }
  15. }
  16. return NULL;
  17. }

 查找测试

  1. //查找测试
  2. void Test3()
  3. {
  4. SLNode* plist = NULL;
  5. //尾部插入一个数据
  6. SLTPushBack(&plist, 1);
  7. if (SLTFind(plist, 3))
  8. printf("找到了");
  9. else
  10. printf("找不到");
  11. if (SLTFind(plist, 1))
  12. printf("找到了");
  13. else
  14. printf("找不到");
  15. }

 结果如下图所示:

(7). 在pos位置之前插入一个结点

 对pos的位置进行讨论,我们发现pos位置有三种情况,一中是头结点,一种是中间结点,还有一种是尾结点。

 在插入前需要对指针进行检查,assert也有几种情况

一种是对三个都检查一遍

    assert(pphead);//有必要断言,防止乱传
    assert(*pphead);//可以不断言
    assert(pos);

另外一种是 

    //宽松的条件
    //pos 和 *pphead 要么都是空要么都不是
    assert((!pos && !(*pphead))||(pos && *pphead));

第一种断言主要是针对pos,严格限制pos一定是链表里的一个有效节点 ,第二种就会稍微宽松点。

为pos的位置进行讨论后发现中间结点和尾结点都一样,所以将头结点和后面的两种情况进行探讨。

假设pos指向头结点

针对头结点或者是开始链表为空的情况,发现pos和pphead指向的都是第一个结点,所以条件为二者相等的情况。即pphead == pos 这种情况同样也能解决二者都为空的情况,这样使用头插就能解决

假设pos指向中间结点或者尾结点

 我们需要找到pos的上一个结点(这是单向链表的缺陷)然后让上一个prev结点指向下一个新结点,下一个结点指向pos结点。

  1. //在pos之前插入
  2. void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
  3. {
  4. //限制pos一定是链表里的一个有效节点
  5. assert(pphead);//有必要断言,防止乱传
  6. //assert(*pphead);//可以不断言
  7. //assert(pos);
  8. //宽松的条件
  9. //pos 和 *pphead 要么都是空要么都不是
  10. assert((!pos && !(*pphead))||(pos && *pphead));
  11. //头插
  12. if (*pphead == pos)//也能解决两个都为空的情况
  13. {
  14. SLTPushFront(pphead, x);
  15. }
  16. else
  17. {
  18. //找前一个结点位置
  19. SLNode* prev = *pphead;
  20. SLNode* newnode = CreateNode(x);
  21. while (prev->next != pos)
  22. {
  23. prev = prev->next;
  24. }
  25. //插入
  26. prev->next = newnode;
  27. newnode->next = pos;
  28. }
  29. }

插入测试 

  1. //插入测试
  2. void Test4()
  3. {
  4. SLNode* plist = NULL;
  5. //第一种情况,plist和pos都为空
  6. SLTInsert(&plist,NULL,3);
  7. //尾部插入四个数据
  8. SLTPushBack(&plist, 1);
  9. SLTPushBack(&plist, 2);
  10. SLTPushBack(&plist, 3);
  11. SLTPushBack(&plist, 4);
  12. SLNode* pos = SLTFind(plist, 4);
  13. SLTInsert(&plist, pos, 40);
  14. //报错测试
  15. //pos = SLTFind(plist, 400);
  16. //SLTInsert(&plist, pos, 40);
  17. //打印
  18. SLTPrint(plist);
  19. }

 结果如下图所示

 

 (8).删除pos结点

 删除pos结点还是得分情况,一种是只有一个结点,一种是多个结点,因为当只有一个结点的时候,prev其实会走到空。

  1. //删除pos结点
  2. void SLTErase(SLNode** pphead, SLNode* pos)
  3. {
  4. //针对pphead是每个都要检查的
  5. assert(pphead);
  6. assert(*pphead);
  7. assert(pos);
  8. //删除pos结点仍然需要找到pos的前一个位置
  9. if (*pphead == pos)
  10. {
  11. //头删
  12. SLTPopFront(pphead);
  13. }
  14. else
  15. {
  16. SLNode* prev = *pphead;
  17. while (prev->next != pos)
  18. {
  19. prev = prev->next;
  20. }
  21. prev->next = pos->next;
  22. free(pos);
  23. pos = NULL;
  24. }
  25. }

(9). 在pos位置后插入

在pos位置后插入删除,修改pos位置的指针即可,跟plist没什么关系,不需要修改plist 

在这里需要先让newnode指向下一个结点,然后再让pos指向新节点。 

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

(10).在pos位置后删除

删除pos后的位置,用一个游标cur保存一下也可以

 但是这里存在空指针的情况,有可能pos->next为空指针,也有可能pos->next->next为空。所以还是需要对pos->next进行相对应的检查。

  1. //删除在pos位置后
  2. void SLTEraseAfter(SLNode* pos)
  3. {
  4. assert(pos);
  5. SLNode* cur = pos->next;
  6. pos->next = pos->next->next;
  7. free(cur);
  8. cur = NULL;
  9. }

在pos位置后的插入和删除测试 

  1. void Test6()
  2. {
  3. SLNode* plist = NULL;
  4. //尾部插入四个数据
  5. SLTPushBack(&plist, 1);
  6. SLTPushBack(&plist, 2);
  7. SLTPushBack(&plist, 3);
  8. SLTPushBack(&plist, 4);
  9. //在pos后插入
  10. SLNode* pos = SLTFind(plist, 2);
  11. SLTInsertAfter(pos,5);
  12. SLTPrint(plist);
  13. //在pos后插入
  14. pos = SLTFind(plist, 5);
  15. SLTInsertAfter(pos, 6);
  16. SLTPrint(plist);
  17. //在pos后删除
  18. pos = SLTFind(plist, 2);
  19. SLTEraseAfter(pos);
  20. pos = SLTFind(plist, 3);
  21. SLTEraseAfter(pos);
  22. //报错测试
  23. /*pos = SLTFind(plist, 3);
  24. SLTEraseAfter(pos);*/
  25. SLTPrint(plist);
  26. }

 结果如下图所示:

(11).链表的销毁

 链表的销毁,用游标遍历链表,然后不断释放空间,最后将这个头指针置空。 

那在这里要注意保存地址。 

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

 (12).完善代码,进行补全断言的操作

 不详细展开了,把pphead的断言补上,包括一些其他的断言检查补全就好了。

四、物理结构和逻辑结构

在涉及到单向链表的打印的时候,我们看到链表打印的逻辑结构如下图所示:

那什么是逻辑结构?

就是我们把链表画成用箭头相链接的结点的序列,结点之间用箭头表示链域中的指针。

为什么用逻辑结构表示链表?

这是因为我们在使用链表的时候,关心的只是它所表示的线性表中数据元素之间的逻辑顺序,而不是每个数据元素在存储器中的实际位置。

那链表的物理结构是什么样子的呢?

链表的物理结构如下图所示:

实际上是每个指针都有一个地址,然后内存空间也有一个结点地址,指针先去找结点地址,通过结点中的指针而找到下一个结点。

五、总结以及单向链表的源码分享

断言:这个值不能为空就断言 

单向链表的优劣性 

优势:

不需要移动大量的数据 

劣势:

失去了随机下标访问的优势。

单向链表的尾插尾删效率不好

单向链表在某些功能上如果要找到上一个结点的画,需要用到另外一个指针,降低效率。

那代码的话仍然是分为三个文件,SList.h,SList.c,test.c

SList.h的代码如下:

  1. #pragma once
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<assert.h>
  5. typedef int SLNDataType;
  6. typedef struct SListNode
  7. {
  8. SLNDataType val;
  9. struct SListNode* next;
  10. }SLNode;
  11. void SLTPrint(SLNode* phead);
  12. void SLTPushBack(SLNode** pphead, SLNDataType x);
  13. void SLTPushFront(SLNode** pphead, SLNDataType x);
  14. void SLTPopBack(SLNode** pphead);
  15. void SLTPopFront(SLNode** pphead);
  16. SLNode* SLTFind(SLNode* phead, SLNDataType x);
  17. void SLTInsert(SLNode**pphead,SLNode* pos, SLNDataType x);
  18. void SLTInsertAfter(SLNode* pos, SLNDataType x);
  19. void SLTErase(SLNode** pphead, SLNode* pos);
  20. void SLTEraseAfter(SLNode* pos);
  21. void SLTDestroy(SLNode** pphead);

 SList.c的代码如下:

  1. #include"SList.h"
  2. void SLTPrint(SLNode* phead)
  3. {
  4. SLNode* cur = phead;
  5. while (cur != NULL)
  6. {
  7. printf("%d ", cur->val);
  8. cur = cur->next;
  9. }
  10. printf("\n");
  11. }
  12. SLNode* CreateNode(SLNDataType x)
  13. {
  14. SLNode* newNode = (SLNode*)malloc(sizeof(SLNode));
  15. if (newNode == NULL)
  16. {
  17. perror("malloc fail");
  18. exit(-1);
  19. }
  20. newNode->val = x;
  21. newNode->next = NULL;
  22. return newNode;
  23. }
  24. void SLTPushBack(SLNode** pphead, SLNDataType x)
  25. {
  26. assert(pphead);
  27. SLNode* newnode = CreateNode(x);
  28. if (*pphead == NULL)
  29. {
  30. *pphead = newnode;
  31. }
  32. else
  33. {
  34. SLNode* tail = *pphead;
  35. while (tail->next != NULL)
  36. {
  37. tail = tail->next;
  38. }
  39. tail->next = newnode;
  40. }
  41. }
  42. void SLTPushFront(SLNode** pphead, SLNDataType x)
  43. {
  44. assert(pphead);
  45. SLNode* newnode = CreateNode(x);
  46. newnode->next = *pphead;
  47. *pphead = newnode;
  48. }
  49. void SLTPopBack(SLNode** pphead)
  50. {
  51. assert(pphead);
  52. assert(*pphead);
  53. SLNode* tail = *pphead;
  54. while (tail->next->next != NULL)
  55. {
  56. tail = tail->next;
  57. }
  58. free(tail->next);
  59. tail->next = NULL;
  60. }
  61. void SLTPopFront(SLNode** pphead)
  62. {
  63. assert(*pphead);
  64. SLNode* tmp = (*pphead)->next;
  65. free(*pphead);
  66. *pphead = tmp;
  67. }
  68. SLNode* SLTFind(SLNode* phead, SLNDataType x)
  69. {
  70. SLNode* cur = phead;
  71. while (cur)
  72. {
  73. if (cur->val == x)
  74. {
  75. return cur;
  76. }
  77. else
  78. {
  79. cur = cur->next;
  80. }
  81. }
  82. return NULL;
  83. }
  84. void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
  85. {
  86. assert(pphead);
  87. assert(*pphead);
  88. assert(pos);
  89. if (*pphead == pos)
  90. {
  91. SLTPushFront(pphead, x);
  92. }
  93. else
  94. {
  95. SLNode* prev = *pphead;
  96. SLNode* newnode = CreateNode(x);
  97. while (prev->next != pos)
  98. {
  99. prev = prev->next;
  100. }
  101. prev->next = newnode;
  102. newnode->next = pos;
  103. }
  104. }
  105. void SLTErase(SLNode** pphead, SLNode* pos)
  106. {
  107. assert(pphead);
  108. assert(*pphead);
  109. assert(pos);
  110. if (*pphead == pos)
  111. {
  112. SLTPopFront(pphead);
  113. }
  114. else
  115. {
  116. SLNode* prev = *pphead;
  117. while (prev->next != pos)
  118. {
  119. prev = prev->next;
  120. }
  121. prev->next = pos->next;
  122. free(pos);
  123. pos = NULL;
  124. }
  125. }
  126. void SLTInsertAfter(SLNode* pos, SLNDataType x)
  127. {
  128. assert(pos);
  129. SLNode* newnode = CreateNode(x);
  130. newnode->next = pos->next;
  131. pos->next = newnode;
  132. }
  133. void SLTEraseAfter(SLNode* pos)
  134. {
  135. assert(pos);
  136. assert(pos->next);
  137. SLNode* cur = pos->next;
  138. pos->next = pos->next->next;
  139. free(cur);
  140. cur = NULL;
  141. }
  142. void SLTDestroy(SLNode** pphead)
  143. {
  144. assert(pphead);
  145. SLNode* cur = *pphead;
  146. while (cur)
  147. {
  148. SLNode* tmp = cur->next;
  149. free(cur);
  150. cur = tmp;
  151. }
  152. *pphead = NULL;
  153. }

test.c的代码如下:

  1. #include"SList.h"
  2. //插入测试
  3. void Test1()
  4. {
  5. SLNode* plist = NULL;
  6. //尾插测试
  7. SLTPushBack(&plist, 1);
  8. SLTPushBack(&plist, 2);
  9. SLTPushBack(&plist, 3);
  10. SLTPushBack(&plist, 4);
  11. //头插测试
  12. SLTPushFront(&plist, 5);
  13. SLTPushFront(&plist, 6);
  14. SLTPushFront(&plist, 7);
  15. SLTPushFront(&plist, 8);
  16. //打印
  17. SLTPrint(plist);
  18. }
  19. //删除测试
  20. void Test2()
  21. {
  22. SLNode* plist = NULL;
  23. //尾部插入四个数据
  24. SLTPushBack(&plist, 1);
  25. SLTPushBack(&plist, 2);
  26. SLTPushBack(&plist, 3);
  27. SLTPushBack(&plist, 4);
  28. //尾删测试
  29. SLTPopBack(&plist);
  30. SLTPrint(plist);
  31. //尾删测试
  32. SLTPopBack(&plist);
  33. SLTPrint(plist);
  34. //头删测试
  35. SLTPopFront(&plist);
  36. SLTPrint(plist);
  37. }
  38. //查找测试
  39. void Test3()
  40. {
  41. SLNode* plist = NULL;
  42. //尾部插入一个数据
  43. SLTPushBack(&plist, 1);
  44. if (SLTFind(plist, 3))
  45. printf("找到了");
  46. else
  47. printf("找不到");
  48. if (SLTFind(plist, 1))
  49. printf("找到了");
  50. else
  51. printf("找不到");
  52. }
  53. //插入测试
  54. void Test4()
  55. {
  56. SLNode* plist = NULL;
  57. //第一种情况,plist和pos都为空
  58. SLTInsert(&plist,NULL,3);
  59. //尾部插入四个数据
  60. SLTPushBack(&plist, 1);
  61. SLTPushBack(&plist, 2);
  62. SLTPushBack(&plist, 3);
  63. SLTPushBack(&plist, 4);
  64. SLNode* pos = SLTFind(plist, 4);
  65. SLTInsert(&plist, pos, 40);
  66. //报错测试
  67. //pos = SLTFind(plist, 400);
  68. //SLTInsert(&plist, pos, 40);
  69. //打印
  70. SLTPrint(plist);
  71. }
  72. //删除测试
  73. void Test5()
  74. {
  75. SLNode* plist = NULL;
  76. //尾部插入四个数据
  77. SLTPushBack(&plist, 1);
  78. SLTPushBack(&plist, 2);
  79. SLTPushBack(&plist, 3);
  80. SLTPushBack(&plist, 4);
  81. SLNode* pos = SLTFind(plist, 3);
  82. SLTErase(&plist, pos);
  83. pos = SLTFind(plist, 1);
  84. SLTErase(&plist, pos);
  85. pos = SLTFind(plist, 4);
  86. SLTErase(&plist, pos);
  87. //打印
  88. SLTPrint(plist);
  89. }
  90. void Test6()
  91. {
  92. SLNode* plist = NULL;
  93. //尾部插入四个数据
  94. SLTPushBack(&plist, 1);
  95. SLTPushBack(&plist, 2);
  96. SLTPushBack(&plist, 3);
  97. SLTPushBack(&plist, 4);
  98. //在pos后插入
  99. SLNode* pos = SLTFind(plist, 2);
  100. SLTInsertAfter(pos,5);
  101. SLTPrint(plist);
  102. //在pos后插入
  103. pos = SLTFind(plist, 5);
  104. SLTInsertAfter(pos, 6);
  105. SLTPrint(plist);
  106. //在pos后删除
  107. pos = SLTFind(plist, 2);
  108. SLTEraseAfter(pos);
  109. pos = SLTFind(plist, 3);
  110. SLTEraseAfter(pos);
  111. //报错测试
  112. /*pos = SLTFind(plist, 3);
  113. SLTEraseAfter(pos);*/
  114. //销毁测试
  115. SLTDestroy(&plist);
  116. SLTPrint(plist);
  117. }
  118. int main()
  119. {
  120. //Test1();
  121. //Test2();
  122. //Test3();
  123. //Test4();
  124. //Test5();
  125. Test6();
  126. return 0;
  127. }

感谢各位同伴的支持,本期单链表讲解就结束啦,在文章的末尾有源代码分享,有需要的小伙伴可自取哈。

下期预告:【数据结构与算法】第六篇双向链表 

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

闽ICP备14008679号