当前位置:   article > 正文

单链表(增删改查)【超详细】_单链表删除

单链表删除

目录

单链表

1.单链表的存储定义

2.结点的创建

3.链表尾插入结点

4.单链表尾删结点

 5.单链表头插入结点 

6.单链表头删结点

7.查找元素,返回结点

 8.在pos结点前插入一个结点

​编辑

 9.在pos结点后插入一个结点

10.删除结点

11.删除pos后面的结点

12.修改链表结点的值

13.打印链表

 14.销毁链表


线性表的链式存储:链表

前言:

之前介绍过线性表的顺序存储方式:顺序表  发现顺序表在 插入删除操作需要移动大量元素;当静态顺序表长度变化较大时,难以确定存储空间的容量;造成存储空间的碎片。

数据结构中: 

注意 这里的是没有哨兵卫的链表。 

单链表

1.单链表的存储定义

单链表的存储与顺序表的存储不一样,单链表不仅仅存放数值,还要存放下一个结点的地址

代码 

  1. typedef int SLNDataType;
  2. typedef struct SListNode
  3. {
  4. SLNDataType val; //存放单链表的值
  5. struct SListNode* next; //存放下一个单链表的地址
  6. }SLNode;

当有了单链表的存储定义,我们就可以对单链表进行存放结点。 

2.结点的创建

这里使用malloc函数进行创建

代码

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

3.链表尾插入结点

尾插结点:这里需要考虑两方面。

1、单链表为空,插入结点的情况,这里直接让头指针指向创建的新结点即可。

2、单链表不为空时,例如下图要尾插结点4

这里只需创建一个尾指针tail 遍历到第三个结点,把 第三个结点的next 指向 第四个结点即可

代码

  1. //尾插结点
  2. void SListPushBack(SLNode** pphead, SLNDataType x)
  3. {
  4. assert(pphead);
  5. SLNode* newnode = CreateNode(x); //创建一个结点
  6. if (*pphead == NULL) //单链表为空直接指向新结点
  7. {
  8. *pphead = newnode;
  9. }
  10. else
  11. {
  12. SLNode* tail = *pphead; //定义一个尾指针
  13. while (tail->next != NULL) //找到表尾
  14. {
  15. tail = tail->next; //指向下一个结点
  16. }
  17. //找到表尾后指向新结点即可
  18. tail->next = newnode;
  19. }
  20. }

注意这里的二级指针。*pphead == phead,指向第一个结点。 

4.单链表尾删结点

单链表尾部删除结点:分两种情况。1)只有一个结点的情况   2)多个结的情况

因为平时删除结点,对于单链表,我们需要找到前面的结点。所以这里采取经典的双指针的方法

定义一个指向当前的结点(cur),一个指向前面的结点(pre),当遍历到表尾时,释放cur指向的结点后,把pre->next = NULL,这样就完成了尾删一个结点。

但是我们发现只有一个结点时

这里free(cur) ,但是pre指向NULL, 这条语句 pre->next = NULL 就是错的。

当然有些人会可能想到,为什么不让pre也指向第一个结点,如果这样想,这里显然是对free()这知识点模糊不清 。因为free(cur) 时 第一个结点的内存空间已经释放返还给操作系统了,所以pre此时指向的内存空间,属于访问野指针了。

所以我们对 1)只有一个结点的情况   2)多个结点的情况 分别处理

1)一个结点的情况

  1. if ((*pphead)->next == NULL) //只有一个结点的情况
  2. {
  3. free(*pphead);
  4. *pphead = NULL;
  5. }

2) 多个结点的情况

定义一个指向当前的结点(cur),一个指向前面的结点(pre),当遍历到表尾时,释放cur指向的结点后,把pre->next = NULL,这样就完成了尾删一个结点。

  1. SLNode* cur = *pphead;
  2. SLNode* pre = NULL;
  3. while (cur->next != NULL)
  4. {
  5. pre = cur;
  6. cur = cur->next;
  7. }

  1. free(cur);
  2. cur = NULL;
  3. pre->next = NULL;

 代码

  1. //尾删结点
  2. void SListPopBack(SLNode** pphead)
  3. {
  4. assert(pphead);
  5. assert(*pphead); //避免链表为空
  6. if ((*pphead)->next == NULL) //只有一个结点的情况
  7. {
  8. free(*pphead);
  9. *pphead = NULL;
  10. }
  11. else //多个结点的情况
  12. {
  13. SLNode* cur = *pphead;
  14. SLNode* pre = NULL;
  15. while (cur->next != NULL)
  16. {
  17. pre = cur;
  18. cur = cur->next;
  19. }
  20. free(cur);
  21. cur = NULL;
  22. pre->next = NULL;
  23. }
  24. }

当然上方代码也可以不用定义pre,来实现

  1. //尾删结点
  2. void SListPopBack(SLNode** pphead)
  3. {
  4. assert(*pphead); //避免链表为空
  5. if ((*pphead)->next == NULL) //只有一个结点的情况
  6. {
  7. free(*pphead);
  8. *pphead = NULL;
  9. }
  10. else //多个结点的情况
  11. {
  12. SLNode* cur = *pphead;
  13. while (cur->next->next != NULL)
  14. {
  15. cur = cur->next;
  16. }
  17. free(cur->next);
  18. cur->next = NULL;
  19. }
  20. }

 5.单链表头插入结点 

首先创建一个新结点,然后让新结点指向 头指针指向的结点,头指针再指向新结点。

注意先后指向的顺序不能变。

代码

  1. //头插结点
  2. void SListPushFront(SLNode** pphead, SLNDataType x)
  3. {
  4. assert(pphead);
  5. SLNode* newnode = CreateNode(x); //创建一个新结点
  6. newnode->next = *pphead; //新结点指向 头指针指向的结点
  7. *pphead = newnode; //再让头指针指向新结点
  8. }

这样就变成

 如果指向的顺序变了,那就会出现这种错误

结点的next指向自己,这样是错误的

6.单链表头删结点

单链表头删除结点时,先临时创建一个next指针来指向第一个结点的下一个结点,然后释放第一个结点,再让头指针指向next,这样就完成删除第一个结点。

代码

  1. //头删结点
  2. void SListPopFront(SLNode** pphead)
  3. {
  4. assert(pphead);
  5. assert(*pphead);
  6. SLNode* next = (*pphead)->next;
  7. free(*pphead);
  8. *pphead = next;
  9. }

方法二

当然也可以先临时存放第一个结点,让*pphead指向第二个结点,然后再删除第一个结点。

代码

  1. //头删结点
  2. void SListPopFront(SLNode** pphead)
  3. {
  4. assert(pphead);
  5. assert(*pphead);
  6. SLNode* tmp = *pphead;
  7. *pphead = (*pphead)->next;
  8. free(tmp);
  9. }

7.查找元素,返回结点

代码

  1. //查找元素,返回结点
  2. SLNode* SListFind(SLNode* phead, SLNDataType x)
  3. {
  4. SLNode* cur = phead;
  5. while (cur)
  6. {
  7. if (cur->val == x)
  8. return cur; //查找成功返回当前结点
  9. cur = cur->next;
  10. }
  11. return NULL; //查找失败返回NULL
  12. }

 8.在pos结点前插入一个结点

assert(phead&&pos); 这里的意思是避免是空指针的情况

 首先,在某结点前插入结点,当在第一个结点前插入结点时,这相当于表头插入结点;当在不是第一个结点前插入结点时,定义一个pre的指针遍历找到pos结点的前一个结点。

代码

  1. //在pos结点前插入一个结点
  2. void SListInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
  3. {
  4. //避免指针为空
  5. assert(pphead);
  6. assert(*pphead);
  7. assert(pos);
  8. if (*pphead == pos)
  9. {
  10. SListPushFront(pphead,x);
  11. return;
  12. }
  13. SLNode* newnode = CreateNode(x);
  14. SLNode* pre = *pphead; //定义一个指向第一个结点的指针
  15. while (pre->next != pos)
  16. {
  17. assert(pre);//避免空指针
  18. pre = pre->next;
  19. }
  20. pre->next = newnode;
  21. newnode->next = pos;
  22. }

图解

注意更改顺序 

上述说的是在有头指针的情况下进行在pos结点前插入一个结点。

题目变形 

当在没有给头指针的情况下,在pos的结点前如何插入一个结点?

解析:这里给出一个取巧的方法,即,先在pos后面插入结点,然后再把pos的值和新插入结点的值交换一下。这样就完成了在没有头指针的情况下在pos前插入一个结点。

代码

  1. //在pos结点前插入一个结点
  2. void SListInsert(SLNode* pos, SLNDataType x)
  3. {
  4. //避免指针为空
  5. assert(pos);
  6. SLNode* newnode = CreateNode(x);
  7. //先在pos后插入结点
  8. newnode->next = pos->next;
  9. pos->next = newnode;
  10. //交换两个结点的值
  11. SLNDataType tmp = pos->val;
  12. pos->val = newnode->val;
  13. newnode->val = tmp;
  14. }

图解

 9.在pos结点后插入一个结点

这里定义一个指针next用于记录pos后面结点的地址,pos指向newnode, newnode指向next。

 代码

  1. //在pos结点后插入结点
  2. void SListInsertAfter(SLNode* pos, SLNDataType x)
  3. {
  4. assert(pos);
  5. SLNode* next = pos->next; //先记录pos后面的结点
  6. SLNode* newnode = CreateNode(x);
  7. pos->next = newnode;
  8. newnode->next = next;
  9. }

 

也可以这样写,注意顺序,避免指向自己。

  代码

  1. //在pos结点后插入结点
  2. void SListInsertAfter(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, 在只有一个结点中删除结点,在多个结点中删除结点。

在只有一个结点中删除,那就相当于头删结点。

在多个结点中删除结点,那就需要知道被删除结点的前一个结点。这里采用双指针的思想进行删除结点。pre指针负责记录pos的前一个结点,next指针记录pos后面的结点。这样释放pos结点后,pre指向next,就完成了pos结点的删除

 代码

  1. //删除结点pos
  2. void SListErase(SLNode** pphead, SLNode* pos)
  3. {
  4. assert(pphead);
  5. assert(pos);
  6. assert(*pphead);
  7. //pos为第一个结点且是第一个结点
  8. if (*pphead == pos)
  9. {
  10. //相当于头删结点
  11. SListPopFront(pphead);
  12. }
  13. else
  14. {
  15. //在多个结点中删除结点pos
  16. SLNode* pre = *pphead;
  17. SLNode* next = pos->next;
  18. while (pre->next != pos)
  19. {
  20. pre = pre->next;
  21. }
  22. free(pos);
  23. pos = NULL;
  24. pre->next = next;
  25. }
  26. }

图解 

11.删除pos后面的结点

首先断言一下,避免头指针和pos后面的结点为空。(assert(phead && pos->next);)

 代码

  1. //删除pos之后的一个结点
  2. void SListEraseAfter(SLNode* pos)
  3. {
  4. assert(pos);
  5. assert(pos->next); //避免为空指针
  6. //先使用next记录pos后面的结点
  7. SLNode* next = pos->next->next;
  8. free(pos->next);
  9. pos->next = next;
  10. }

图解

12.修改链表结点的值

  代码

  1. //修个pos结点中的值
  2. void SListModify(SLNode* phead, SLNode* pos, SLNDataType x)
  3. {
  4. assert(phead&&pos); //避免为空指针
  5. SLNode* cur = phead; //cur指向头指针
  6. while(cur != pos)
  7. {
  8. cur = cur->next;
  9. }
  10. cur->val = x; //修个值
  11. }

13.打印链表

  代码

  1. //打印结点
  2. void SListPrint(SLNode* phead)
  3. {
  4. SLNode* cur = phead;
  5. while (cur)
  6. {
  7. printf("%d->",cur->val);
  8. cur = cur->next;
  9. }
  10. printf("NULL\n");
  11. }

 14.销毁链表

这里因为是使用mallco开辟的空间,所以是需要进行手动释放的。

注意:可能是多个结点,也就是开辟了多个不连续内存空间

所以,首先应该记录被释放结点的下一个结点的地址,避免释放后找不到下一个结点

释放完所有的结点,头指针置为NULL

 代码

  1. //销毁链表
  2. void SListDestory(SLNode** pphead)
  3. {
  4. assert(pphead);
  5. SLNode* p = *pphead;
  6. //注意结点的内存空间的释放,要释放多个
  7. while (p)
  8. {
  9. //首先应该记录被释放结点的下一个结点的地址,避免释放后找不到下一个结点
  10. SLNode* tmp = p->next;
  11. free(p);
  12. p = tmp; //指向下一个结点
  13. }
  14. //释放完所有的结点,头指针置为NULL
  15. *pphead = NULL;
  16. }

总结:

单链表在找出位置的指针后,插入和删除时间复杂度为O(1),

但在查找方面上,单链表的时间复杂度为O(n),

当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构。

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

闽ICP备14008679号