数据结构——单链表(Singly Linked List)
1.链表介绍
链表是一种物理储存上非连续、非顺序的存储结构。数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。

对于上图,每一个结点都是一个结构体,这个结构体内存储的成员是值和下一个结点的地址。所以plist存储的是第一个结点的地址(即plist为一个结构体指针),而后续每个节点都会存储对于自己下一个结点的地址。由于链表的结点是通过malloc一个个申请出来的,所以其存储空间是不连续的,也是由于这个原因我们才需要通过一个结构体指针来访问逻辑上的下一个结点。
2.链表的分类
链表具有三个特征,根据这三个特征的排列组合得知链表有8种组合。
2.1 单向和双向

单向链表的每个结点成员只有指向下一个结点的指针,而没有指向上一个结点的指针。双向链表则二者皆有,所以单向链表就像“过河的卒”,不可以回头访问自己的上一个结点,而双向链表则既可以访问自己的上一个结点,也可以访问自己的下一个结点。
2.2 带头和不带头

带头链表和不带头链表的区别就在于带头链表有一个“头结点”。
总结下来,带头链表会有一些优势:
① 避免空指针的访问:因为头结点又名哨兵位,所以它起到一个放哨的作用。无论链表结构是否为空,都可以放心的访问链表,因为有头结点的存在,就算链表为空也不会访问到空指针,更加安全。
② 函数传参不需要传递二级指针:如果没有头结点,当我对链表增删查改操作时,由于手上拿的是链表的第一个结点,所以如果操作到链表第一个位置,可能会涉及到链表第一个结点指针的变化,而传一级指针的参数是不会使该操作生效的,所以需要二级指针。但是有了头结点后无论做什么操作,头结点一旦生成直至链表销毁都不会改变,所以不需要传递二级指针,更加方便。
2.3 循环和非循环

非循环链表的尾结点由于是最后一个结点,其后没有结点,所以它的next指针是指向NULL的。循环链表的尾结点则是指回了链表的第一个结点,使得整体结构体现出循环的功能。
尽管有八种链表的组合形式,但是我们常用的的只有两种形式的链表。即单向不带头不循环链表和双向带头循环链表。

3.单链表工程
对于一个单链表工程,我们一般模式需要分为三部分。
SList.h 为头文件,其中包含库函数头文件的包含,顺序表结构体的定义与声明,接口函数的声明。
SList.c 包含接口函数的定义。
Test.c 是我们的测试源文件,从这里进入main函数。
3.1 单链表的定义
建立一个单链表需要多个结点共同构成,我们针对单链表一个结点进行定义,将其定义为一个结构体,包含数据和下一个结点的指针。
typedef int SLNDataType;typedef struct SListNode
{SLNDataType val;struct SListNode* next;
}SLNode;
3.2 单链表的函数接口
对一个单链表,我们需要一些函数来方便我们管理数据。因此我们所要实现的基本的函数接口需要包含增删查改等功能。
3.2.1 单链表结点插入
单链表常用的插入数据方式有:头插,即在链表最前端插入数据结点;尾插,即在链表最后端插入数据结点;随机插入,即在指定的结点前或者后插入结点。
单链表由于没有头结点,所以在插入时很有可能需要改变头结点,例如头插或链表为空时插入。所以在参数传递时我们不得不以二级指针的形式进行传参,这样可以在函数内部修改头结点,从而成功插入结点。
3.2.1.1 单链表创建结点
因为在单链表的函数接口中有多个结点需要插入结点,需要频繁创建结点并以不同的方式插入,所以我们将创建节点的过程封装成一个函数,在需要创建结点时调用函数即可,避免代码冗余。
SLNode* CreateNode(SLNDataType x)
{SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));if (newnode == NULL){perror("malloc");exit(-1);}newnode->val = x;newnode->next = NULL;return newnode;
}
3.2.1.2 单链表头插
单链表头插时只需要将原来的第一个结点链接在新结点之后即可。
void SLTPushFront(SLNode** pphead, SLNDataType x)
{assert(pphead);SLNode* tmp = CreateNode(x);tmp->next = *pphead;*pphead = tmp;
}
3.2.1.3 单链表尾插
单链表尾插需要先找到尾结点,但由于单链表的单向不循环的特性,我们只能从头遍历链表找到尾结点,才能将新结点链接在尾结点之后。
void SLTPushBack(SLNode** pphead, SLNDataType x)
{assert(pphead);if (*pphead == NULL){*pphead = CreateNode(x);}else{SLNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}tail->next = CreateNode(x);}
}
3.2.1.4 单链表在指定结点前插入
在指定节点前插入需要这个结点前一个结点的指针指向新结点,所以我们就需要先遍历找到这个结点的前一个结点。
//在pos节点前插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{assert(pphead);assert(*pphead);assert(pos);SLNode* tmp = CreateNode(x);if (pos == *pphead){tmp->next = *pphead;*pphead = tmp;}else{SLNode* prev = *pphead;tmp->next = pos;while (prev->next != pos){//if (prev->next == NULL)//{// printf("插入节点有误\n");// exit(-1);//}prev = prev->next;}prev->next = tmp;}
}
3.2.1.5 单链表在指定结点后插入
在指定节点后插入就可以不用遍历链表,直接链接即可。
void SLTInsertAfter(SLNode** pphead, SLNode* pos, SLNDataType x)
{assert(pphead);assert(pos);SLNode* tmp = CreateNode(x);tmp->next = pos->next;pos->next = tmp;
}
3.2.2 单链表结点删除
单链表常用的删除结点的方式有:头删,即删除在链表最前端的结点;尾删,即删除在链表最后端的结点;随机删除,即删除在指定的结点前或者后的结点。
3.2.2.1 单链表头删
头删只需要将第一个结点释放掉,然后将原来指向链表第一个结点的变量赋值第二个结点即可。
void SLTPushFront(SLNode** pphead, SLNDataType x)
{assert(pphead);SLNode* tmp = CreateNode(x);tmp->next = *pphead;*pphead = tmp;
}
3.2.2.2 单链表尾删
尾删需要遍历链表找到尾结点,然后将尾结点释放并将尾结点的前一个结点的next指针赋值为空指针。需要警惕只有一个结点的链表,需要将链表置为空。
void SLTPopBack(SLNode** pphead)
{assert(pphead);//0个节点assert(*pphead);//1个节点SLNode* prev = NULL;SLNode* tail = *pphead;if (tail->next == NULL){free(tail);tail = NULL;*pphead = NULL;}else{while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;}
}
3.2.2.3 单链表删除指定结点
想要删除一个指定结点,需要找到其前一个结点,将前一个结点与后一个结点链接起来。
//删除pos节点
void SLTErase(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(*pphead);assert(pos);if (pos == *pphead){SLNode* tmp = *pphead;*pphead = (*pphead)->next;free(tmp);tmp = NULL;}else{SLNode* prev = *pphead;while (prev->next != pos){/*if (prev->next == NULL){printf("删除节点有误\n");exit(-1);}*/prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}
3.2.2.4 单链表在指定结点后删除
删除指定结点后一个结点,只需要将这个结点和它的下下个结点链接起来即可。
//在pos位置后删除
void SLTEraseAfter(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(pos);assert(pos->next);SLNode* tmp = pos->next;pos->next = tmp->next;free(tmp);tmp = NULL;
}
3.2.3 单链表打印
依次遍历所有结点即可,由于不需要改变链表的值,所以可以传递一个一级指针。
void SLTPrint(SLNode* phead)
{while (phead != NULL){printf("%d->", phead->val);phead = phead->next;}printf("NULL\n");
}
3.2.4 单链表查找
用于查找链表中指定值的结点,会返回该结点的地址。所以,只需要遍历链表并且比较即可。
SLNode* SLTFind(SLNode* phead, SLNDataType x)
{SLNode* tmp = phead;while (tmp != NULL){if (tmp->val == x){return tmp;}else{tmp = tmp->next;}}printf("未找到节点\n");return NULL;
}
3.2.5 单链表销毁
销毁单链表只需要遍历链表,依次释放所有结点即可。
void SLTDestroy(SLNode** pphead)
{while (*pphead != NULL){SLNode* tmp = *pphead;*pphead = (*pphead)->next;free(tmp);tmp = NULL;}
}
4.单链表总结反思
无头单向不循环链表,这种链表虽然结构简单但是应用广泛。在最近的学习过程中,我发现不少与链表有关的题目多用单链表来设题,除此之外,一些更加复杂的结构也需要用单链表作为支撑,所以可以看出其重要性。
除此之外,恰恰是因为其结构简单,所以对特殊情况的限制较少,这就需要我在写代码时充分考虑可能会发生的所有情况,以便做出合理控制。毫无疑问,单链表是很重要的,我感觉它就像“原汁原味”的链表,需要反复品尝,并且一切上层建筑都是源于这看似简单的结构。
相关文章:
数据结构——单链表(Singly Linked List)
1.链表介绍 链表是一种物理储存上非连续、非顺序的存储结构。数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。 对于上图,每一个结点都是一个结…...
4面试题--数据库(补充)
隔离性问题 若不考虑隔离性则会出现以下问题 1. 脏读:指⼀个事务在处理数据的过程中,读取到另⼀个 未提交 事务的数据 2. 不可重复读:指对于数据库中的某个数据(同⼀个数据项),⼀个事务内的多次查询却…...
人力资源管理后台 === 左树右表
1.角色管理-编辑角色-进入行内编辑 获取数据之后针对每个数据定义标识-使用$set-代码位置(src/views/role/index.vue) // 针对每一行数据添加一个编辑标记this.list.forEach(item > {// item.isEdit false // 添加一个属性 初始值为false// 数据响应式的问题 数据变化 视图…...
WordPress无需插件禁用WP生成1536×1536和2048×2048尺寸图片
我们在使用WordPress上传图片媒体文件的时候,是不是看到媒体库中有15361536和20482048的图片文件,当然这么大的文件会占用我们的服务器空间,如何禁止掉呢? function remove_default_image_sizes( $sizes) {unset( $sizes[1536x15…...
Git 与 Maven:企业级版本管理与版本控制规范设计
一、背景 当今,许多开发人员熟悉 GitFlow 工作流程,但往往忽略了 GitFlow 如何与 Maven 版本控制结合,尤其是在管理 snapshot 和 release 版本时的最佳实践。本文旨在整合 GitFlow 工作流程与 Maven 版本管理,提出一个统一的企业…...
Springmvc原理解析
1. DispatcherServlet springmvc的核心控制器,负责截获所有的请求,当截获请求后委托给HandlerMapping进行请求映射的解析工作,目的是找到哪一个Controller的方法可以处理该请求,找到后再交由给HandlerAdaptor去负责调用并返回Mod…...
Retrofit怎么返回一个JSON字符串?
项目用已经使用了 Retrofit,定义了接口方法,返回了 JSON 转换后的实体对象,炒鸡方便。但是总有意料之外的时候,比如我不需要返回实体对象,我要返回纯纯的 JSON 字符串,怎么办呢? 先看源码 通过…...
【GCC】2:chatgpt:SendSideBandwidthEstimation
webrtc中SendSideBandwidthEstimation类的设计 The SendSideBandwidthEstimation class in WebRTC is a critical component in its video engine. It’s responsible for deciding the video traffic rate that can be sent without overloading the network and thus maintai…...
OpenGL 自学总结
前言: 本人是工作后才接触到的OpenGL,大学找工作的时候其实比较着急,就想着尽快有个着落。工作后才发现自己的兴趣点。同时也能感觉到自己当前的工作有一点温水煮青蛙的意思,很担心自己往后能力跟不上年龄的增长。因此想在工作之余…...
java集合,ArrayList、LinkedList和Vector,多线程场景下如何使用 ArrayList
文章目录 Java集合1.2 流程图关系1.3 底层实现1.4 集合与数组的区别1.4.1 元素类型1.4.2 元素个数 1.5 集合的好处1.6 List集合我们以ArrayList集合为例1.7 迭代器的常用方法1.8 ArrayList、LinkedList和Vector的区别1.8.1 说出ArrayList,Vector, LinkedList的存储性能和特性1.…...
【2023.11.26】Mybatis自定义映射规则学习
创建自定义映射规则 <select id"selectArtist" resultMap"test">select * from artist </select> 在SQL语句标签中将resultType修改为resultMap,即自定义映射的id。 编写自定义映射规则: <resultMap id"tes…...
Nginx(九) aio sendfile directio 组合使用测试(2)
测试7:开启directio2m、sendfile,关闭aio,请求/vendor.js {"time_iso8601":"2023-11-26T22:47:3508:00","request_uri":"/vendor.js","status":"200","bytes_sent":…...
使用ETLCloud实现CDC实时数据集成:从MySQL到ClickHouse的实时数据同步
背景 在上一篇文章中体验了 ETLCloud 的离线数据迁移功能,就像大数据领域里有离线计算和实时计算, ETLCloud 还提供了基于 CDC (Change Data Capture)的实时数据集成功能:实时数据集成是指通过变化数据捕获技术&#…...
【云平台】STM32微信小程序阿里云平台学习板
【云平台】STM32微信小程序阿里云平台学习板 文章目录 前言一、立创EDA(硬件设计)1.主控STM32F103C8T62.ESP8266模块3.温湿度模块4.光照强度模块5.OLED显示模块6.PCB正面7.PCB反面8.3D视角正面9.3D视角反面 二、【云平台】STM32微信小程序阿里云平台学习…...
【研究中2】sql server权限用户设置
--更新时间2023.11.26 21:30 负责人:jerrysuse DBAliCMSIF EXISTS (select * from sysobjects where namehkcms_admin)--判断是否存在此表DROP TABLE hkcms_adminCREATE TABLE hkcms_admin (id int identity(1, 1),--id int primary key identity…...
从零开始学习管道:管道程序的优化和文件描述符继承问题
📟作者主页:慢热的陕西人 🌴专栏链接:Linux 📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言 本博客主要内容管道后续的完善,以及解决管道继承多个文件描…...
【JavaWeb】HTMLCSSJavaScript
HTML&CSS&JavaScript 文章目录 HTML&CSS&JavaScript一、开发工具及在线帮助文档二、 HTML2.1 HTML&CSS&JavaScript的作用2.2 HTML基础结构2.3 HTML概念词汇解释2.4 HTML的语法规则2.5 常用标签 三、CSS3.1 引入方式3.2 CSS选择器3.3 CSS浮动3.4 CSS定位…...
如何在没有备份的情况下恢复 iPhone 上已删除的短信
要在没有备份的情况下恢复 iPhone 上已删除的消息,您可以从“消息”应用程序恢复它们或使用第三方数据恢复工具。 虽然我们的 iPhone 可以做很多事情,但我在设备上最常做的事情之一就是文本。无论我是与朋友或家人联系,还是分享重要信息&…...
tomcat-pass-getshell 弱口令 漏洞复现
tomcat-pass-getshell 弱口令 漏洞复现 名称: tomcat-pass-getshell 弱口令 描述: Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。 通过弱口令登…...
利用 LD_PRELOAD 环境变量
文章目录 原理LD_PRELOAD介绍如何上传.so文件 例题 [虎符CTF 2022]ezphp 原理 LD_PRELOAD介绍 LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...
