深入理解链表(SList)操作
目录:
- 一、 链表介绍
- 1.1、 为什么引入链表
- 1.2、 链表的概念及结构
- 1.3、 链表的分类
- 二、 无头单向非[循环链表](https://so.csdn.net/so/search?q=循环链表&spm=1001.2101.3001.7020)的实现
- 2.1、 [单链表](https://so.csdn.net/so/search?q=单链表&spm=1001.2101.3001.7020)的定义
- 2.2、 动态申请一个节点
- 2.3、 销毁(释放)所有节点
- 2.4、 打印单链表
- 2.5、 单链表尾插
- 2.6、 单链表头插
- 2.7、 单链表尾删
- 2.8、 单链表头删
- 2.9、 在单链表中查找指定值的节点
- 2.10、 单链表在pos位置之后插入
- 2.11、 单链表删除指定pos位置的节点
- 2.12、 单链表删除指定pos位置之后的节点
- 2.13、 求单链表长度
- 2.14、 判断单链表是否为空
- 三、 总结
学习链表之前,建议先学习下顺序表,下面是顺序表的直达链接:
深入理解顺序表(SeqList)
一、 链表介绍
1.1、 为什么引入链表
- 学习链表之前,先让我们来思考一个问题:
为什么有了顺序表,还需要有链表这样的数据结构呢?
- 顺序表存在的一些问题:
- 顺序表在中间/头部的插入删除,要挪动很多数据,时间复杂度为O(N),效率太低了。
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是一次增长2倍,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们
再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间SeqList。
- 为了更好的解决上述问题,引入了链表。
1.2、 链表的概念及结构
- 概念
前面学习的顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,而链表是一种物理存储结构上不连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,可以实现更加灵活的动态内存管理。
- 链表的组成
链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
每个结点包括两个部分:
1、数据域:存储数据元素
2、指针域:存储下一个结点地址
- 链表的物理结构
可以看到,4个节点的地址并不是连续的,链表在物理结构上不一定是线性的,而在逻辑结构上是线性的
- 链表的逻辑结构(想象出来的)
- 注意
1、链式结构在逻辑上是连续的,但在物理上不一定连续
2、链表的节点是在堆上申请出来的
1.3、 链表的分类
链表的结构非常多样化
- 单向、双向
- 带头结点、不带头节点(哨兵位的头节点,不存储有效数据)
- 非循环、循环
- 常用的两种结构
二、 无头单向非循环链表的实现
2.1、 单链表的定义
typedef int SLTDataType;//定义单链表节点
typedef struct SListNode
{SLTDataType data; //数据域struct SListNode* next; //指针域
}SLTNode;
2.2、 动态申请一个节点
//动态申请一个节点
SLTNode* BuySLTNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL) //检查是否开辟成功{perror("malloc fail");return;}newnode->data = x;newnode->next = NULL;return newnode;
}
2.3、 销毁(释放)所有节点
//销毁单链表中所有节点
void SLTDestory(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;while (cur != NULL) //遍历链表{SLTNode* next = cur->next; //保存cur的下一个节点free(cur); //释放节点cur = next;}*pphead = NULL; //z
}
- 作用:
销毁单链表中所有节点,指向头节点的指针置空,防止内存泄露等问题
2.4、 打印单链表
//打印单链表
void SLTPrint(SLTNode* phead)
{SLTNode* ptr = phead;while (ptr != NULL){printf("%d->", ptr->data);ptr = ptr->next;}printf("NULL\n");
}
2.5、 单链表尾插
- 先来看一种错误写法:这个也是弄懂单链表的关键
传一级指针的值,用一级指针接收
- 这种写法会导致一个问题:
因为当链表为空时,我们需要改变 plist 的指向,使其指向第一个节点。
而初始 plist 和 phead 都指向 NULL,调用函数后,phead 指向了新的节点,而 plist 还是指向 NULL 的。
- 如何解决呢:
plist 是指向第一个节点的指针,想要在函数中改变 plist 的值(指向),必须要把 plist指针的地址 作为实参传过去,形参用 二级指针 接收,这样在函数中对二级指针解引用得到 plist 的值,就可以改变 plist 的值(指向)了
记住:在函数里面要改变 int,则要传 int* ,要改变 int* ,则要传 int**
- 注意区分开这几个代表的意思哦
平时一般不用解引用两层,没啥意义,一般是解引用一层,来改变外面 plist 的指向。
- **正确写法:**通过二级指针改变外面 plist 的指向
单链表为空时,plist 直接指向新节点;
单链表不为空时,先找到单链表的尾节点,然后将尾节点的next指向新节点
//单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead); //检查参数是否传错SLTNode* newnode = BuySListNode(x); //动态申请一个节点if (*pphead == NULL) //单链表中没有节点时{*pphead = newnode; //plist指向新节点}else if (*pphead != NULL) //单链表中已经有节点时{SLTNode* tail = *pphead;while (tail->next != NULL) //找到单链表中的最后一个节点{tail = tail->next;}tail->next = newnode; //最后一个节点的next指向新节点}
}
- 运行结果如下:
2.6、 单链表头插
- 图解头插操作
- 代码如下
因为要改变外部 plist 的指向,所以用二级指针接收指针 plist 的地址
//单链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead); //检查参数是否传错SLTNode* newnode = BuySListNode(x); //动态申请一个节点newnode->next = *pphead; //新节点的next指针指向plist指向的位置*pphead = newnode; //plist指向头插的新节点
}
- 运行结果如下:
2.7、 单链表尾删
- 图解尾删操作
- 代码如下
单链表只有一个节点时,删除节点,plist 指向 NULL;
单链表有多个节点时,先找到单链表尾节点的上一个节点,删除尾节点,然后将该节点的next指向 NULL;
因为可能要改变外部 plist 的指向,所以用二级指针接收指针 plist 的地址。
//单链表尾删
void SLTPopBack(SLTNode** pphead)
{assert(pphead); //检查参数是否传错assert(*pphead); //断言,链表不能为空SLTNode* tail = *pphead;if ((*pphead)->next == NULL) //链表只有一个节点{free(*pphead); //删除节点*pphead = NULL; //plist置空}else //链表中有多个节点{while (tail->next->next != NULL) //找到链表的尾节点的上一个节点{tail = tail->next;}free(tail->next); //删除尾节点tail->next = NULL; //置空/*思路2:SLTNode* prev = *pphead;while (tail->next) //找到链表的尾节点和它的上一个节点{prev = tail;tail = tail->next;}free(tail); //删除尾节点prev->next = NULL; //置空*/}
}
- 运行结果如下:
2.8、 单链表头删
- 图解头删操作
- 代码如下
因为可能要改变外部 plist 的指向,所以用二级指针接收指针 plist 的地址。
//单链表头删
void SLTPopFront(SLTNode** pphead)
{assert(pphead); //检查参数是否传错assert(*pphead); //链表不能为空SLTNode* cur = *pphead; //保存头节点的地址*pphead = cur->next; //plist指向头节点的下一个节点free(cur); //删除头节点
}
- 运行结果如下:
2.9、 在单链表中查找指定值的节点
如果查找到,返回该节点的地址;没有查找到,返回 NULL。
//在单链表中查找指定值节点
SListNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;//遍历链表while (cur != NULL){if (cur->data == x){return cur; //找到了,返回该节点的地址}cur = cur->next;}return NULL; //未找到,返回NULL
}
2.10、 单链表在pos位置之后插入
- 图解插入操作
- 分析思考为什么不在pos位置之前插入?
单链表不适合在pos位置之前插入,因为需要遍历链表找到pos位置的前一个节点,
单链表更适合在pos位置之后插入,如果在后面插入,只需要知道pos位置就行,会简单很多
C++官方库里面单链表给的也是在之后插入
- 代码如下
//单链表在指定pos位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos); //给的pos位置不能为空SLTNode* newnode = BuySListNode(x); //动态申请一个节点newnode->next = pos->next; //新节点的next指针指向pos位置后一个节点pos->next = newnode; //pos位置的next指向新节点
}
- 作用:
先调用 SLTFind 函数在单链表中查找指定值的节点,查找到了,返回该节点的地址,也即是我们指定的 pos 位置,然后将其传给 SLTInsertAfter 函数。
2.11、 单链表删除指定pos位置的节点
- 图解删除操作
- 代码如下
要考虑到两种情况:pos位置为单链表的第一个节点,pos位置为中间节点;
因为可能要改变外部 plist 的指向,所以用二级指针接收指针 plist 的地址。
//单链表删除指定pos位置的节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(*pphead); //链表不能为空assert(pos); //给的pos位置不能为空//pos位置为第一个节点,相当于头删if (pos == *pphead){SLTPopFront(pphead);}//pos位置为中间节点else{SLTNode* prev = *pphead;while (prev->next != pos) //找到pos位置的前一个节点{prev = prev->next;}prev->next = pos->next; //pos位置的前一个节点指向pos位置的后一个节点free(pos); //释放pos节点pos = NULL; //置空}
}
- 补充一下,assert 断言
assert 放在函数里面检查参数,一方面是为了安全,另一方面也是为了防止有人调用该函数时,不正确的使用,错误传入参数,好及时提醒到他,写代码时一定要考虑到有人不正确的使用该函数时的场景,来进行避免
- 测试其功能
先调用 SLTFind 函数在单链表中查找指定值的节点,查找到了,返回该节点的地址,也即是我们指定的 pos 位置,然后将其传给 SLTErase 函数。
2.12、 单链表删除指定pos位置之后的节点
- 图解删除操作
- 代码如下
//单链表删除指定pos位置之后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos); //给的pos位置不能为空assert(pos->next); //给的pos位置不能是尾节点SLTNode*del = pos->next; //保存pos位置的后一个节点pos->next = pos->next->next;free(del); //释放pos位置的后一个节点del=NULL;
}
-运行结果如下:
2.13、 求单链表长度
//求单链表长度
int SLTSize(SLTNode* phead)
{int size = 0;SLTNode* cur = phead;while (cur != NULL) //遍历链表{size++;cur = cur->next;}return size;
}
2.14、 判断单链表是否为空
plist为空,返回 1 (true),非空,返回 0 (false)
//单链表判空
bool SLTEmpty(SLTNode* phead)
{//plist为空,返回1(true),非空,返回0(false)return phead == NULL;/*写法2:return phead == NULL ? true : false;*/
}
三、 总结
单链表是一种简单且高效的数据结构,适用于需要频繁插入和删除操作的场景。通过掌握单链表的创建、插入、删除、查找、修改和遍历等操作,我们可以更好地理解和应用这一数据结构,从而提高程序的效率和可靠性。
相关文章:

深入理解链表(SList)操作
目录: 一、 链表介绍1.1、 为什么引入链表1.2、 链表的概念及结构1.3、 链表的分类 二、 无头单向非[循环链表](https://so.csdn.net/so/search?q循环链表&spm1001.2101.3001.7020)的实现2.1、 [单链表](https://so.csdn.net/so/search?q单链表&spm1001.2…...
03. prometheus 监控 Linux 主机
文章目录 一、prometheus 监控 Linux 主机二、防火墙打开端口1. 方式一:使用 iptables 添加白名单(推荐使用):2. 方式二:重载防火墙 一、prometheus 监控 Linux 主机 1. 官网下载 node_exporter 官网:htt…...

AI占据2024诺贝尔两大奖项,是否预示着未来AI即一切?
本次诺贝尔物理学和学奖的获得者都与AI息息相关,可谓是“AI领域的大丰收”。 2024年诺贝尔物理学奖揭晓:瑞典皇家科学院公布了2024年诺贝尔物理学奖的获得者。他们是美国的约翰霍普菲尔德(John J. Hopfield),以及加拿…...

[已解决] Install PyTorch 报错 —— OpenOccupancy 配环境
目录 关于 常见的初始化报错 环境推荐 torch, torchvision & torchaudio cudatoolkit 本地pip安装方法 关于 OpenOccupancy: 语义占用感知对于自动驾驶至关重要,因为自动驾驶汽车需要对3D城市结构进行细粒度感知。然而,现有的相关基准在城市场…...

6. PH47 代码框架硬件开发环境搭建
概述 PH47代码框架的硬件开发环境搭建同样简单, 建立基本的 PH47 框架学习或二次开发的硬件开发环境所需设备如下: BBP 飞控板及相关软硬件: BBP飞控板,或者至少一块Stm32F411核心板(WeAct Studio)Stm32程序烧录工具…...
package.json配置
package.json配置 描述配置文件配置脚本配置依赖配置发布配置系统配置第三方配置 描述配置 name : 项目名称,第三方包可以通过npm install 包名安装 "name":"react"version : 项目版本,项目版本号 "version" : "18.2…...

视频怎么转gif动图?5个简单转换方法快来学(详细教程)
相信大家在社交平台上会经常看到一些有趣的gif动图表情包,有些小伙伴就会问:这些GIF动图是如何制作的呢?一般GIF动图表情包可以用视频来制作,今天小编就来给大家分享几个视频转成GIF动图的方法,相信通过以下的几个方法…...

10月更新:优维EasyOps®需求解决更彻底,功能体验再升级
升 级 不 止 步 欢迎来到 需求至上,功能完善 的 \ EasyOps 7.5版本 / 👇 >> 联动架构视图:深度融合监控与资源拓扑 传统上,依赖监控态势感知系统固有的分层拓扑结构虽有其优势,但在处理复杂系统尤其是核心数…...
黑马javaWeb笔记重点备份1:三层架构、IOC、DI
来自:【黑马程序员JavaWeb开发教程,实现javaweb企业开发全流程(涵盖SpringMyBatisSpringMVCSpringBoot等)】 https://www.bilibili.com/video/BV1m84y1w7Tb/?p75&share_sourcecopy_web&vd_source9332b8fc5ea8d349a54c398…...

大坝渗流监测设备——渗压计
渗压计是一种用于监测大坝等水工建筑物渗流压力的重要设备,其准确性和可靠性对于保障大坝安全运行至关重要。南京峟思将为大家详细介绍渗压计的工作原理、安装方法及其在大坝渗流监测中的应用。 渗压计主要利用振弦频率的变化来测量渗透水压力。设备由透水部件、感应…...

Pikachu-Sql Inject-宽字节注入
基本概念 宽字节是相对于ascII这样单字节而言的;像 GB2312、GBK、GB18030、BIG5、Shift_JIS 等这些都是常说的宽字节,实际上只有两字节 GBK 是一种多字符的编码,通常来说,一个 gbk 编码汉字,占用2个字节。一个…...

如何制作低代码开发的视频教程?
如何制作低代码开发的视频教程? 随着数字化转型的加速,越来越多的企业和组织开始采用低代码开发平台来加速应用程序的构建。对于许多开发者和业务人员来说,学习如何使用这些平台可以显著提高工作效率。因此,创建一份清晰、实用且…...
Flink学习地址
--基础概念 概览 | Apache Flink --应用系列 如何学习Flink:糙快猛的大数据之路(图文并茂)_flink 学习-CSDN博客 --Python系列 pyflink实时接收kafka数据至hive_pyflink下kafka数据经过处理后插入hive-CSDN博客 Pyflink教程(一)&#…...
05_23 种设计模式之《建造者模式》
文章目录 一、建造者模式基础知识建造者模式的结构建造者模式的应用场景 一、建造者模式基础知识 建造者模式(Builder Pattern)是一种创建型设计模式,它提供了一种优雅的方式来创建复杂对象,同时隐藏其构建过程。这种模式允许你通…...

详细分析Spring Security OAuth2中的JwtAccessTokenConverter基本知识(附Demo)
目录 前言1. 基本知识2. Demo3. 实战 前言 java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)【Java项目】实战CRUD的功能整理(持续更新) 1. 基本知识 JwtAccessTokenConverter 是 Spring Security OAuth2 中的一…...

python习题2
1、输出一个年份,判断其是不是闰年 #输入一个年份,判断其是否是闰年 y eval(input()) if y%4 0 and y%100 ! 0:print("是") elif y%4000:print("是") else:print("不是") 2、模拟智能客服: 按1查询账户余额…...

CVSS 4.0 学习笔记
通用漏洞评分系统(CVSS)捕获了主要技术软件、硬件和固件漏洞的特征。其输出包括数字分数,表明与其他漏洞。 以下因素可能包括但不限于:监管要求、客户数量受影响、因违约造成的金钱损失、生命或财产受到威胁,或潜在漏洞的声誉影响。这些因素在CVSS评估范围之外。 CVSS的好…...
解决 GPTQ 模型导入后推理生成 Tokens 速度很慢的问题(从源码重新安装 Auto-GPTQ)
这里解决的是使用 Auto-GPTQ 或者 Transformers 导入 GPTQ 模型后推理速度很慢的问题。 值得注意的是,这个问题很有可能是因为安装不正确,所以 GPTQ 无法正确使用 GPU 进行推理,也就是说无法进行加速,即便 print(model.device) 显…...

NDC美国药品编码目录数据库查询方法
NDC(National Drug Code)翻译为“国家药品代码”,是美国食品药品监督管理局(FDA)制定的一种药品标识系统,用于唯一标识药品。这个编码系统主要目的是为精准识别和追踪不同药品而建设,行业人员和…...
vue3的v-model使用
vue3的v-model使用 单个绑定值 子组件 props: [‘modelValue’], emits: [‘update:modelValue’], 注:modelValue是默认的,如果只有一个需要绑定v-model,可使用modelValue 此时父组件写法 <CustomInput v-model"searchText"…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...