数据结构——单链表(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),它允许你定义在程序运行前优先加载的动态链接…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...
STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...
上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式
简介 在我的 QT/C 开发工作中,合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式:工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...
如何配置一个sql server使得其它用户可以通过excel odbc获取数据
要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据,你需要完成以下配置步骤: ✅ 一、在 SQL Server 端配置(服务器设置) 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到:SQL Server 网络配…...
