当前位置:   article > 正文

数据结构——单向链表(C语言实现)

数据结构——单向链表(C语言实现)

目录

什么是链表?

什么是单向链表

单向链表的声明和定义

单向链表元素的插入(在pos前插入)

解惑

单向链表元素的插入(在pos后插入)

单向链表节点的建立

单向链表元素的打印

单向链表元素的删除(后元素)

单向链表元素的删除(前元素)

单向链表特定位置的删除

单向链表后一位置的删除

单向链表元素的查找

单向链表元素的删除

测试


什么是链表?

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

实则可以看做一个一个车厢连接在一起,车厢里的元素可以看做人,也就是节点里存储的数据,元素。

注意:

1. 链式结构在逻辑上是连续的,但是在物理结构上是不连续的

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

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


此篇文章先介绍无头单向非循环链表

什么是单向链表

无头链表:以结点为开头的链式结构——————同时也是非循环链表

单向链表的声明和定义

  1. typedef int SLTDataType;
  2. typedef struct SListNode//定义的是结点的结构
  3. {
  4. SLTDataType data;//存放数据
  5. struct SListNode* next;//存放下一个位置的地址
  6. }SLTNode;
  7. void SLTPrint(SLTNode* phead);
  8. void SLTPopFront(SLTNode** phead);
  9. void SLTPopBack(SLTNode** phead);
  10. SLTNode* STFind(SLTNode* phead, SLTDataType x);
  11. void SLInsert(SLTNode* pphead, SLTNode* pos, SLTDataType x);//pos就是节点的指针
  12. void SLInsertAfter(SLTNode* pos, SLTDataType x);
  13. void SLErase(SLTNode* pphead, SLTNode* pos);
  14. void SLEraseAfter(SLTNode* pos);
  15. void SLDestroy(SLTNode* phead);

单向链表元素的插入(在pos前插入)

我们要知道,链表本质是一个个结点连接起来的,所以可以看做是多个指针连接起来的。

与顺序表不同:顺序表里定义的指针是指向一个动态开辟的数组

                        链表本身就是由多个指针构成的结点相互连接形成的链式结构。

所以在主函数里定义的时候,顺序表直接定义变量,链表是要定义一个指针变量

  1. void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
  2. {
  3. assert(pphead);
  4. assert(*pphead);
  5. assert(pos);
  6. if (*pphead == pos)
  7. {
  8. SLTPushFront(pphead, x);//若为初始位置,那么就直接头插
  9. }
  10. else
  11. {
  12. SLTNode* prev = *pphead;
  13. while (prev->next != pos)
  14. {
  15. prev = prev->next;
  16. }
  17. SLTNode* newnode = BuyLTNode(x);
  18. prev->next = newnode;
  19. newnode->next = pos;//将newnode后边的接上pos处的数据
  20. }
  21. }

解惑

1.为什么接收的结构体指针临时变量是用" ** "定义?

——因为我们定义的是一个结构体指针,若是想要改变一个指针本身,那么就需要传入结构体指针的地址,所以我们需要用到二级指针。

2.为什么接收的结构体指针的位置是用" * "定义?

——在顺序表中,我们可以直接用下标来定到要改变元素的位置,但是链表是由指针组成的,因此我们不能用下标来定位。所以需要一个指针来定位,传入,,这里仅是用来定位,所以用一级指针来接收。

3. 插入的具体过程是什么?

——由于链表不能直接用下标定位元素位置,所以我们只能通过指针来定位

——因此要找到插入的位置pos,那我们就需要用一个while循环,直到定位到pos前的一个节点,然后将这个节点定位到新建立的节点上,新建立的节点连接在原pos的节点上。

单向链表元素的插入(在pos后插入)

因为单向链表可以直接定位到下一个节点的位置,所以不需要定位到前一个结点的位置(指针)。因此我们这里不需要传入整个结构体的指针,定位到pos处的指针即可。

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

单向链表节点的建立

建立一个节点,我们需要给予其一个空间来存放,那怎么建立空间?——malloc函数来建立。

并且节点指向的下一个节点为空

  1. SLTNode* BuyLTNode(SLTDataType x)
  2. {
  3. SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  4. //将这个元素给予一个空间,创建一个结点
  5. if (newnode == NULL)
  6. {
  7. perror("malloc fail");
  8. return NULL;
  9. }
  10. newnode->data = x;
  11. newnode->next = NULL;
  12. return newnode;
  13. }

单向链表元素的打印

我们需要一个接收一个结构体指针来进行遍历。

但是我们不能用原指针来进行遍历,因为这样子遍历到后面,那么头指针就会指向最后一个元素,会导致前面的元素都丢失。

  1. void SLTPrint(SLTNode* phead)//传入的是结构体,所以用一个指针来接收结构体的地址
  2. {
  3. STNode* cur=phead;//若是*进行了解引用的话,那么就是指向结构体
  4. while(cur!=NULL)
  5. {
  6. printf("%d",cur->data);
  7. cur=cur->next;
  8. }
  9. printf("NULL\n");
  10. }

单向链表元素的删除(后元素)

因为要对链表进行改变,所以我们要传入结构体指针地址,所以需要用一个二级指针来接收地址。

  1. void SLTPopBack(SLTNode** phead)
  2. {
  3. //因为**phead接收的是地址,所以*phead是结构体的指针
  4. //没有节点(空链表)
  5. if (*phead == NULL)
  6. {
  7. return;
  8. }
  9. if ((*phead)->next == NULL)
  10. {
  11. free(*phead);
  12. *phead = NULL;
  13. }
  14. else
  15. {
  16. SLTNode* tail = *phead;
  17. // 找尾部
  18. while (tail->next->next)//找到倒数第二个,也就是数据中的最后一位数据
  19. {
  20. tail = tail->next;
  21. }
  22. free(tail->next);
  23. tail->next=NULL;
  24. }
  25. }

单向链表元素的删除(前元素)

  1. void SLTPopFront(SLTNode** pphead)
  2. {
  3. assert(pphead);// 链表为空,则pphead也不会为空,所以需要断言——结构体
  4. assert(*pphead);// 链表为空,不能头删,那么此时需要断言——结构体指针
  5. SLTNode* del = *pphead;
  6. *pphead = del->next;//将头指针指向下一个元素的地址
  7. free(del);
  8. }

单向链表特定位置的删除

  1. void SLErase(SLTNode** pphead, SLTNode* pos)
  2. {
  3. assert(pphead);//判断结构体不为空
  4. assert(pos);//判断该位置的指针不为空
  5. if (pos == *pphead)//若为起始的结点,那么就直接删除第一个结点
  6. {
  7. SLTPopFront(pphead);
  8. }
  9. else
  10. {
  11. SLTNode* prev = *pphead;
  12. while (prev->next!=pos)//下一个不是要查找的位置时
  13. {
  14. prev = prev->next;//则往下
  15. }
  16. prev->next = pos->next;//把下一个元素的地址直接复制为下两位,等于跳过下一个数字
  17. free(pos);//释放开辟的空间
  18. }
  19. }

单向链表后一位置的删除

  1. void SLEraseAfter(SLTNode* pos)
  2. {
  3. assert(pos);
  4. SLTNode* prve = pos->next;
  5. pos->next = prve->next;
  6. free(prve);//释放开辟的内存
  7. }

单向链表元素的查找

同理,不能用下标锁定,所以只能逐一遍历,并且要返回指针

  1. SLTNode* STFind(STNode* phead,SLTDataType x)
  2. {
  3. assert(phead);
  4. STNode* cur=phead;
  5. while(cur)
  6. {
  7. if(cur->data==x)
  8. {
  9. return cur;//返回此处地方的指针
  10. }
  11. cur=cur->next;
  12. }
  13. return NULL;
  14. }

单向链表元素的删除

因为链表是由一个个指针连接的,所以我们仅需要删除指针即可。

  1. void SLDestroy(SLTNode* phead)//接受结构体的地址,结构体的地址就是首元素的地址
  2. {
  3. assert(phead);
  4. SLTNode* cur=phead;
  5. while(cur)
  6. {
  7. SLTNode* next=cur->next;
  8. free(cur);
  9. cur=next;
  10. }
  11. phead=NULL;//最后将头指针定义为空指针,置空
  12. }

测试

  1. void TestSList1()
  2. {
  3. //phead和newnode都是局部变量,该怎么办?
  4. SLTNode* plist = NULL;//是个结构体指针
  5. //改变数据要传入数据地址才能同时改变实参的数据
  6. SLTPushBack(&plist, 1);//拷贝出的是形参,形参改变后不会改变实参
  7. SLTPushBack(&plist, 2);
  8. SLTPushBack(&plist, 3);
  9. SLTPushBack(&plist, 4);
  10. SLTPushBack(&plist, 4);
  11. SLTPopBack(&plist);
  12. SLTPrint(plist);//这里不需要改变内容,故传入指针(指向的起始内容地址)就行
  13. SLTNode* pos = STFind(plist, 3);//STFind返回的是结构体的指针,故可以通过pos去改变plist
  14. if (pos)//判断是否为空
  15. {
  16. SLInsert(&plist, pos,30);
  17. //Fine恰好与pos进行协同使用
  18. }
  19. SLTPrint(plist);
  20. }

由于此处plist是一个结构体指针,所以

若是想改变结构体的指针,就需要传入结构体指针的地址,所以需要用二级指针来接收

——**pphead

如果只是想进行遍历和结构体的改变,那么就传入结构体的地址即可。

——用一级指针来接收就行了,因为不对指针进行改变,所以不需要用到二级指针。

难点:

主要还是考察了二级指针和一级指针的不同用法,以及在改变链表里的元素时,不同形参的选择

若此处搞懂了,那么单向链表就可以熟练掌握了。

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

闽ICP备14008679号