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

目录

单链表

1.单链表的存储定义

2.结点的创建

3.链表尾插入结点

4.单链表尾删结点

 5.单链表头插入结点 

6.单链表头删结点

7.查找元素,返回结点

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

​编辑

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

10.删除结点

11.删除pos后面的结点

12.修改链表结点的值

13.打印链表

 14.销毁链表


线性表的链式存储:链表

前言:

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

数据结构中: 

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

单链表

1.单链表的存储定义

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

代码 

typedef int SLNDataType;typedef struct SListNode 
{SLNDataType val;	//存放单链表的值struct SListNode* next; //存放下一个单链表的地址
}SLNode;

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

2.结点的创建

这里使用malloc函数进行创建

代码

//创建一个结点
SLNode* CreateNode(SLNDataType x) 
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("CreateNode->malloc");exit(-1);}newnode->val = x;  //结点赋值newnode->next = NULL; //结点下一个结点为NULLreturn newnode;
}

3.链表尾插入结点

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

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

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

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

代码

//尾插结点
void SListPushBack(SLNode** pphead, SLNDataType x) 
{assert(pphead);SLNode* newnode = CreateNode(x);    //创建一个结点if (*pphead == NULL)	//单链表为空直接指向新结点{*pphead = newnode;}else  {SLNode* tail = *pphead;	//定义一个尾指针while (tail->next != NULL)	//找到表尾{tail = tail->next;	//指向下一个结点}//找到表尾后指向新结点即可tail->next = newnode;}
}

注意这里的二级指针。*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)一个结点的情况

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

2) 多个结点的情况

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

		SLNode* cur = *pphead;SLNode* pre = NULL;while (cur->next != NULL){pre = cur;cur = cur->next;}

		free(cur);cur = NULL;pre->next = NULL;

 代码

//尾删结点
void SListPopBack(SLNode** pphead) 
{assert(pphead);assert(*pphead); //避免链表为空if ((*pphead)->next == NULL)  //只有一个结点的情况{free(*pphead);*pphead = NULL;}else  //多个结点的情况{SLNode* cur = *pphead;SLNode* pre = NULL;while (cur->next != NULL){pre = cur;cur = cur->next;}free(cur);cur = NULL;pre->next = NULL;}
}

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

//尾删结点
void SListPopBack(SLNode** pphead) 
{assert(*pphead); //避免链表为空if ((*pphead)->next == NULL)  //只有一个结点的情况{free(*pphead);*pphead = NULL;}else  //多个结点的情况{SLNode* cur = *pphead;while (cur->next->next != NULL){cur = cur->next;}free(cur->next);cur->next = NULL;}
}

 5.单链表头插入结点 

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

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

代码

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

这样就变成

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

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

6.单链表头删结点

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

代码

//头删结点
void SListPopFront(SLNode** pphead) 
{assert(pphead);assert(*pphead);SLNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}

方法二

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

代码

//头删结点
void SListPopFront(SLNode** pphead) 
{assert(pphead);assert(*pphead);SLNode* tmp = *pphead;*pphead = (*pphead)->next;free(tmp);
}

7.查找元素,返回结点

代码

//查找元素,返回结点
SLNode* SListFind(SLNode* phead, SLNDataType x) 
{SLNode* cur = phead;while (cur){if (cur->val == x)return cur;	//查找成功返回当前结点cur = cur->next;}return NULL; //查找失败返回NULL
}

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

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

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

代码

//在pos结点前插入一个结点
void SListInsert(SLNode** pphead, SLNode* pos, SLNDataType x) 
{//避免指针为空assert(pphead);assert(*pphead); assert(pos);if (*pphead == pos){SListPushFront(pphead,x);return;}SLNode* newnode = CreateNode(x);SLNode* pre = *pphead;  //定义一个指向第一个结点的指针while (pre->next != pos){assert(pre);//避免空指针pre = pre->next;}pre->next = newnode;newnode->next = pos;
}

图解

注意更改顺序 

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

题目变形 

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

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

代码

//在pos结点前插入一个结点
void SListInsert(SLNode* pos, SLNDataType x) 
{//避免指针为空assert(pos);SLNode* newnode = CreateNode(x);//先在pos后插入结点newnode->next = pos->next;pos->next = newnode;//交换两个结点的值SLNDataType tmp = pos->val;pos->val = newnode->val;newnode->val = tmp;
}

图解

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

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

 代码

//在pos结点后插入结点
void SListInsertAfter(SLNode* pos, SLNDataType x) 
{assert(pos);SLNode* next = pos->next;	//先记录pos后面的结点SLNode* newnode = CreateNode(x);pos->next = newnode;newnode->next = next;
}

 

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

  代码

//在pos结点后插入结点
void SListInsertAfter(SLNode* pos, SLNDataType x) 
{assert(pos);SLNode* newnode = CreateNode(x);newnode->next = pos->next;pos->next = newnode;
}

10.删除结点

例:删除结点pos, 在只有一个结点中删除结点,在多个结点中删除结点。

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

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

 代码

//删除结点pos
void SListErase(SLNode** pphead, SLNode* pos) 
{assert(pphead);assert(pos);assert(*pphead);//pos为第一个结点且是第一个结点if (*pphead == pos){//相当于头删结点SListPopFront(pphead);}else{//在多个结点中删除结点posSLNode* pre = *pphead;SLNode* next = pos->next;while (pre->next != pos){pre = pre->next;}free(pos);pos = NULL;pre->next = next; }}

图解 

11.删除pos后面的结点

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

 代码

//删除pos之后的一个结点
void SListEraseAfter(SLNode* pos) 
{assert(pos);assert(pos->next); //避免为空指针//先使用next记录pos后面的结点SLNode* next = pos->next->next;free(pos->next);pos->next = next;
}

图解

12.修改链表结点的值

  代码

//修个pos结点中的值
void SListModify(SLNode* phead, SLNode* pos, SLNDataType x)
{assert(phead&&pos); //避免为空指针SLNode* cur = phead; //cur指向头指针while(cur != pos){cur = cur->next;}cur->val = x; //修个值
}

13.打印链表

  代码

//打印结点
void SListPrint(SLNode* phead) 
{SLNode* cur = phead;while (cur){printf("%d->",cur->val);cur = cur->next;}printf("NULL\n");
}

 14.销毁链表

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

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

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

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

 代码

//销毁链表
void SListDestory(SLNode** pphead) 
{assert(pphead);SLNode* p = *pphead;//注意结点的内存空间的释放,要释放多个while (p) {//首先应该记录被释放结点的下一个结点的地址,避免释放后找不到下一个结点SLNode* tmp = p->next; free(p);p = tmp; //指向下一个结点}//释放完所有的结点,头指针置为NULL*pphead = NULL;
}

总结:

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/137069.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

flink1.18.0 sql-client报错

报错 Flink SQL> > > select * from t1; [ERROR] Could not execute SQL statement. Reason: java.lang.ClassNotFoundException: org.apache.kafka.clients.consumer.OffsetResetStrategy 解决 注意 一定要重启flink服务 否则还会报错: Flink SQL> select *…

CDN是什么?

一.CDN的概念 内容分发网络(Content Delivery Network,简称CDN)是建立并覆盖在承载网之上,由分布在不同区域的边缘节点服务器群组成的分布式网络。CDN应用广泛,支持多种行业、多种场景内容加速,例如&#…

拆分代码 + 动态加载 + 预加载,减少首屏资源,提升首屏性能及应用体验

github 原文地址 我们看一些针对《如何提升应用首屏加载体验》的文章,提到的必不可少的措施,便是减少首屏幕加载资源的大小,而减少资源大小必然会想到按需加载措施。本文提到的便是一个基于webpack 插件与 react 组件实现的一套研发高度自定…

【算法与数据结构】39、LeetCode组合总和

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析:这道题当中数字可以多次使用,那么我们在递归语句当中不能直接找下一个candidate的元素&…

Android Studio导入,删除第三方库

Android项目经常用到无私的程序员们提供的第三方类库。本篇博客就是实现第三方库的导入和删除。 一、导入第三方库 1、将需要的库下载到本地; 2、新建Moudle (1)File --- New Moudle (2)选择Android Library --- Next (3)填写Moudle名 --- Finish。一个新的Mou…

[RCTF 2019]nextphp

文章目录 考点前置知识PHP RFC:预加载FFI基本用法PHP RFC:新的自定义对象序列化机制 解题过程 考点 PHP伪协议、反序列化、FFI 前置知识 PHP RFC:预加载 官方文档 通过查看该文档,在最下面找到预加载结合FFI的危害 FFI基本用法 …

ElementUI的Dialog弹窗实现拖拽移动功能

实现ElementUI的Dialog弹窗可以拖拽移动 实现步骤: 1.创建自定义指令 在utils文件夹下新建文件夹 utils/directive/el-dragDialog/index.js import drag from ./dragconst install function(Vue) {Vue.directive(el-drag-dialog, drag) }if (window.Vue) {windo…

基于自然语言处理的结构化数据库问答机器人系统

温馨提示:文末有 CSDN 平台官方提供的学长 Wechat / QQ 名片 :) 1. 项目简介 知识库,就是人们总结出的一些历史知识的集合,存储、索引以后,可以被方便的检索出来供后人查询/学习。QnA Maker是用于建立知识库的工具,使用…

企业组建客服中心,需要考虑哪些问题?

随着市场竞争的加剧,企业越来越注重客户服务。因此,组建一个专业的客服中心已成为企业的一个重要战略举措。然而,这个任务不仅需要考虑技术和人员方面的问题,还需要考虑许多其他的因素。在本文中,我们将探讨企业在组建…

【java】【MyBatisPlus】【四】【完】MyBatisPlus一些实战总结(枚举、翻页、sql、组合条件、自增主键、逻辑删除)

目录 一、枚举 1、数据库type字段是Integer 类型枚举 2、创建一个该字段的枚举类 TypeEnum 3、修改实体类 4、配置文件新增mybatis-plus的配置 5、检验: 5.1 查询显示 5.3 库里验证 二、自增主键不是id字段处理 三、逻辑删除字段不是delete字段处理 1、实…

CTFHUB-WEB-SQL注入

sql学的太不好了&#xff0c;回炉重造 判断 Sql 注入漏洞的类型&#xff1a; 1.数字型 当输入的参 x 为整型时&#xff0c;通常 abc.php 中 Sql 语句类型大致如下&#xff1a;select * from <表名> where id x这种类型可以使用经典的 and 11 和 and 12 来判断&#xff…

SQL优化之MySQL执行计划(Explain)及索引失效详解

1、执行计划基础 1.1、执行计划&#xff08;Explain&#xff09;定义 在 MySQL 中可以通过 explain 关键字模拟优化器执行 SQL语句&#xff0c;从而解析MySQL 是如何处理 SQL 语句的。 1.2、MySQL查询过程 客户端向 MySQL 服务器发送一条查询请求服务器首先检查查询缓存&am…

【Unity】简单案例脚本实现 | 鼠标观察/键盘控制移动飞行/行走/碰撞检测

《Unity5实战-使用C#和Unity开发多平台游戏》第二章-构建一个让你置身3D空间的演示 鼠标观察/键盘控制移动飞行/行走/碰撞检测 Unity版本&#xff1a;2019.4.23f1c1 注意脚本名称和组件添加&#xff0c;不在文章中一一强调场景模型都是在资源商店选择的免费下载&#xff08;选…

windows下nvm的安装和使用

1、nvm简介 nvm是一个nodejs的版本管理工具&#xff0c;它可以在同一台机器上安装和切换不同版本的node的工具。常用在一台机器不同项目需要不同的node环境时&#xff0c;对node版本的自由切换使用。 2、nvm下载安装 2.1 下载 地址&#xff1a; https://github.com/coreybu…

设计模式——建造者模式

目录 建造者模式盖房项目需求基本介绍四个角色实例代码注意事项和细节抽象工厂模式 VS 建造者模式 建造者模式 盖房项目需求 传统方式&#xff1a;打地基&#xff0c;砌墙&#xff0c;封顶 盖房子步骤 public abstract class AbstractHouse {// 地基public abstract void b…

【GUI软件开发】小红书评论采集:自动采集1w多条,含二级评论!

文章目录 一、爬取目标1.1 效果截图1.2 演示视频1.3 软件说明 二、代码讲解2.1 爬虫采集模块2.2 软件界面模块2.3 日志模块 三、附完整源码及软件 一、爬取目标 您好&#xff01;我是马哥python说 &#xff0c;一名10年程序猿。 我用python开发了一个爬虫采集软件&#xff0c…

【优选算法系列】【专题二滑动窗口】第二节.1004. 最大连续1的个数 III和1658. 将 x 减到 0 的最小操作数

文章目录 前言一、最大连续1的个数 III 1.1 题目描述 1.2 题目解析 1.2.1 算法原理 1.2.2 代码编写二、将 x 减到 0 的最小操作数 2.1 题目描述 2.2 题目解析 2.2.1 算法原理 2.2.2 代码编写总结 前言 一、最大连…

【halcon】halcon 函数文件 以及 脚本引擎如何调用外部函数文件 上篇

前言 halcon有几种文件&#xff1a; 本地程序函数&#xff08;.hdev&#xff09;外部函数文件&#xff08;.hdvp)库函数(.hdp) 说多了容易混淆&#xff0c;今天就说&#xff0c;我觉得最有用的&#xff1a;外部函数文件&#xff08;.hdvp) 步骤 先写一段halcon脚本&#x…

宝塔部署QQ机器人,提示OpenSSL 1.0.2k-fips 26 Jan 2017

1、报错预览 Traceback (most recent call last):File "/www/wwwroot/python/bot-one/main.py", line 5, in <module>import requestsFile "/www/wwwroot/python/bot-one/343ae0eb0d491a10a1a00c0621b03ed0_venv/lib/python3.9/site-packages/requests/_…

粤嵌实训医疗项目(小组开发)--day05

目录 一、医生功能模块 ------------前端实现------------ ------------后端接口------------ 功能一&#xff1a;分页查询医生基础信息&#xff08;介绍MybatisPlus如何使用分页&#xff09; 功能二&#xff1a;根据搜索栏名称查找对应医生&#xff08;讲解自定义查询集&…