C语言—单链表

目录

一、链表的概念及结构

二、单链表实现

(2.1)基本结构定义

(2.2)申请节点

(2.3)打印函数

(2.4)头部插入删除\尾部插入删除

(2.4.1)尾部插入

(2.4.2)头部插入

(2.4.3)尾部删除

(2.4.4)头部删除

(2.5)查找

(2.6)在指定位置之前插入数据

(2.7)删除pos节点

(2.8)在指定位置之后插入数据

(2.9)删除pos之后的节点

(2.10)销毁链表

三、链表的分类


一、链表的概念及结构

概念:链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

链表的结构跟⽕⻋⻋厢相似,如图所示:

与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/节点”。节点的组成主要有两个部分:当前节点要保存的数据和保存下⼀个节点的地址(指针变量)。图中指针变量 plist 保存的是第⼀个节点的地址,我们一般称之为头指针。链表中每个节点都是独⽴申请的(即需要插⼊数据时才去申请⼀块节点的空间),所以我们需要通过指针变量来保存下⼀个节点位置才能从当前节点找到下⼀个节点。

补充说明:
1、链式机构在逻辑上是连续的,在物理结构上不⼀定连续
2、节点⼀般是从堆上申请的
3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续

二、单链表实现

注意:这里的单链表指的是不带头单向不循环链表(后面会介绍链表的分类)

(2.1)基本结构定义

为了单链表的复用性,这里将 int 重命名为SLTDataType,单链表节点中包含一个数据域(data),一个指针域(next),数据域存放需要存放的数据,指针域存放下一个节点的地址。这里为了方便后续使用又将struct SListNode 重命名为SLTNode。

(2.2)申请节点

后续插入节点的函数都需要先申请节点,所以这里直接写一个申请节点的函数,方便后面使用,在该函数中,首先用malloc申请一块节点大小的空间,然后判断是否申请成功,如果申请失败,用perror函数报错,然后结束程序,如果申请成功,将数据插入,并将指针域置空,然后返回节点地址。(形参是要插入的数据)

(2.3)打印函数

为了后续测试中方便查看单链表中的数据,我们这里实现一个打印函数,功能是打印单链表中的所有数据。

具体实现:

解释:形参是指向整个单链表头部的指针,要打印单链表中的数据,首先单链表不能为空,如果是空表没有数据可打印,所以先断言头指针不为空,然后定义一个临时变量 cur 记录链表头部的地址(即将头指针赋给cur),然后以 cur 作为循环条件,每次进入循环,打印当前节点存储的数据,打印后将指针向后移动,当打印完最后一个节点数据后,指针移动到NULL,循环结束。最后可以再打印一个换行。

(2.4)头部插入删除\尾部插入删除

(2.4.1)尾部插入

具体实现:

思路:遍历找到尾节点,然后将新节点连接上,如果链表为空,直接将新节点作为头。

解释:该函数中有两个形参,一个是二级指针,指向单链表的头指针,一个是要插入的数据,首先这里的二级指针一定不可以为空,如果二级指针为空,我们将无法找到链表,所以先断言二级指针不为空,然后判断当前链表是否为空,如果链表为空,直接将新申请的节点置为表头(即直接让头指针指向该节点,注意这里的pphead是二级指针,要先解引用才是头指针),如果链表不为空,先定义一个临时指针指向链表头部,然后当 cur 的 next 不为空的时候进行循环,出循环时 cur 一定指向尾节点,因为链表中每个节点(除尾节点)中的指针域存的都是下一个节点的地址,而最后一个节点后面已经没有节点了,所以它的指针域存的是NULL,而循环结束的条件就是 cur 指针域为NULL。找到尾节点后,将新申请的节点插入即可。

图解:

测试:

(2.4.2)头部插入

具体实现:

思路:将新节点插在原链表的头的前面,并作为新的头。

解释:该函数中有两个形参,一个是二级指针,指向单链表的头指针,一个是要插入的数据,首先这里的二级指针一定不可以为空,如果二级指针为空,我们将无法找到链表,所以先断言二级指针不为空,然后直接申请新节点,新节点的指针域存原链表的头,然后将新节点赋给头指针,这样新节点就插入到原链表的头部了并且成为新链表的头。

图解:

测试:

(2.4.3)尾部删除

具体实现:

思路:遍历找到尾节点的前一个节点,释放尾节点,然后将尾节点的前一个节点指针域置空,如果链表只有一个节点,直接释放就行,但是要将头指针置空。

解释:该函数只有一个参数,即指向头指针的二级指针,这里和上面一样二级指针一定不可以为空,又因为是删除,链表一定要存在,所以头指针也不可以为空,所以先断言这两个指针不为空,尾删分为两种情况,当链表只有一个节点时,删除后链表为空,这时要将头指针置空,当链表不止一个节点时,通过循环找到尾节点的前一个节点,然后释放最后一个节点,因为尾节点已经被释放了(即将空间还给操作系统了),所以尾节点的前一个节点的 next 指针要置空,成为新的尾节点,这里之所以要找尾节点的前一个节点,而不直接找尾节点也是因为 next 指针置空的问题,如果直接找尾节点,因为是单链表,无法获得它的前驱节点,想要将前驱节点的 next 指针置空就会变麻烦。

图解:

测试:

(2.4.4)头部删除

具体实现:

解释:该函数只有一个参数,即指向头指针的二级指针,和前面一样,二级指针一定不可以为空,又因为该函数的功能是删除,所以链表不可以为空,即头指针不可以为空,所以先断言这两个指针不为空,然后定义一个临时指针变量记录头节点的下一个节点的地址,释放当前头节点,将记录的后一个节点变为新的头节点,这里一定要先去记录后一个节点的位置,否则释放后就找不到了。如果当前链表只有一个节点也没事,因为它的指针域里存的是NULL,释放头节点后就将NULL给了头指针,这时链表就变为空链表了。

图解:

测试:

(2.5)查找

思路:遍历链表,对比数据域,如果相同则找到,返回节点地址,如果遍历结束还没有找到,则链表中没有该数据,返回空。

功能:给出链表的头指针和要查找的数据,该函数会在该链表中进行查找,如果找到,返回存储该数据的节点,如果没找到,返回NULL。

具体实现:

解释:该函数一共两个形参,第一个是链表的头指针,第二个是要查找的数据,因为后面要进行遍历链表,而头指针要始终指向链表头部,不能随意移动,所以先定义一个临时变量记录头指针,然后作为循环条件进行遍历,对比数据,一样就找到了,返回节点,结束函数,不一样就向后移动,访问下一个节点,如果遍历结束还没有找到说明链表中没有该数据,返回NULL。

图解:

测试:

(2.6)在指定位置之前插入数据

具体实现:

解释:该函数有三个参数,第一个是指向头指针的二级指针,第二个是指定节点位置(可以配合查找函数确定),第三个要存储的数据。二级指针和前面一样,还是不可以为空,否则找不到链表,指定位置之前插入,其实就是指定节点之前插入,所以这个节点必须存在,又因为这个节点的存在,所以链表一定不为空,所以先进行这三个条件的断言,这里插入分为两种情况,第一种情况,当pos是头节点的时候,在头节点之前插节点,其实就是前面的头插法,这里可以调用前面写好的头插函数(SLTPushFront),也可以像我一样自己写一下,第二种情况,当pos不是头节点的时候,我们需要遍历链表,找到pos的前驱节点,然后将新节点插入,因为这是单链表,所以我们没法通过pos节点向前插入节点,必须找到他的前驱节点。

图解:

(2.7)删除pos节点

思路:两种情况,第一种:要删除的是头节点时,因为头节点被删除,所以头指针要进行移动。第二种:删除的不是头节点,头指针不需要移动,如果先删除pos节点,我们就没有办法找到pos节点后面的节点了,所以先找到pos节点的前驱节点,然后将pos节点的前驱节点和后面的节点连接起来,再去删除pos节点。

具体实现:

(2.8)在指定位置之后插入数据

思路:首先想在pos位置之后插入数据,pos节点一定要存在,然后先让新节点的指针域存pos节点后面节点的地址,然后再让pos节点的指针域存新节点的地址,顺序不能颠倒,如果先让pos节点指针域存新节点,那么就无法找到pos后面的节点。

具体实现:

(2.9)删除pos之后的节点

思路:删除pos节点之后的节点,pos节点必须存在,正常情况下,pos后面那个要删的节点也必须存在,所以可以对这两个节点都断言一下,这里我用了 if 语句,允许pos后面没有节点,当没有节点时,函数不会做任何事,直接结束,如果有,先将要删除的节点记录下来,然后将pos和要删除的节点的后面的节点连接起来,如果前面没有记录要删除的节点,这里连接后就找不到要删除的节点了。然后释放要删除的节点就可以了。

具体实现:

(2.10)销毁链表

思路:要找到链表,二级指针不能为空,如果当前链表已经为空了,函数结束,不会做任何事;如果链表不为空,定义两个指针,一个记录当前要释放的节点,一个记录释放的节点后面的节点,因为释放后就无法找到它后面的节点了,这里先记录下来,这样就可以在删除节点后将记录要释放节点的指针移动到链表的下一个节点,然后继续释放,直到循环到所有节点都被释放,这里当cur为空时,已经释放了所有节点,next 此时也为空,没必要再向后移动了,所以用 if 判断一下。

具体实现:

三、链表的分类

链表的结构⾮常多样,以下情况组合起来就有8种(2x2x2)链表结构:

虽然有这么多的链表的结构,但是我们实际中最常⽤还是两种结构:单链表和双向带头循环链表。

1.⽆头单向⾮循环链表:结构简单,⼀般不会单独⽤来存数据。实际中更多是作为其他数据结构的⼦结构,如哈希桶、图的邻接表等等。另外这种结构在笔试⾯试中出现很多。
2.带头双向循环链表:结构最复杂,⼀般⽤在单独存储数据。实际中使⽤的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使⽤代码实现以后会发现结构会带来很多优势,实现反⽽简单了。

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

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

相关文章

计算机毕业设计 智慧物业服务系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…

【算法笔记】双指针算法深度剖析

【算法笔记】双指针算法深度剖析 🔥个人主页:大白的编程日记 🔥专栏:算法笔记 文章目录 【算法笔记】双指针算法深度剖析前言一.移动零1.1题目1.2思路分析1.3代码实现 二.复写零2.1题目2.2思路分析2.3代码实现 三.快乐数3.1题目…

【自然语言处理】(1) --语言转换方法

文章目录 语言转换方法一、统计语言模型1. 词向量转换2. 统计模型问题 二、神经语言模型1. 词向量化2. 维度灾难3. 解决维度灾难4. embedding词嵌入5. Word2Vec技术5.1 连续词袋模型(CBOW)5.2 跳字模型(Skip-gram) 总结 语言转换方…

【ssh-xorg】SSH远程配置X11窗口回传

前言 我们通常在进行远程配置板端的时候往往会出现一个问题,在不连接显示屏或者启用VNC服务的前提下(或者使用其他软件提供的功能),我们无法在远程终端看到板端的新窗口,本文提供一种方式,在进行ssh远程连接时候制定参数-CX&…

【大数据】Doris 数据库与表操作语法实战详解

目录 一、前言 二、数据库基本操作 2.1 修改账户密码 2.2 创建新用户 2.3 创建数据库与账户授权 2.3.1 数据库创建补充说明 2.3.2 数据库账户赋权 三、数据表基本操作 3.1 Doris 数据表介绍与使用 3.1.1 建表结构说明 3.1.2 建表语法与操作 3.1.3 建表示例 - 单分区…

探索大型语言模型在文化常识方面的理解能力与局限性

介绍 论文地址:https://arxiv.org/pdf/2405.04655v1 近年来,大型语言模型(LLM)不仅被广泛应用于各个领域,而且通过大量的基准评估,证明它们能够理解人类所拥有的常识(Commonsense)…

pdf怎么编辑修改内容?详细介绍6款pdf编辑器功能

■ pdf怎么编辑修改内容? PDF(Portable Document Format)作为一种广泛使用的文件格式,具有特点包括兼容性强、易于传输、文件安全性高、跨平台性、可读性强、完整性、可搜索性、安全性、可压缩性。 PDF文件本身是不可以直接进行编…

深度学习--------------------------------门控循环单元GRU

目录 门候选隐状态隐状态门控循环单元GRU从零开始实现代码初始化模型参数定义隐藏状态的初始化函数定义门控循环单元模型训练该部分总代码简洁代码实现 做RNN的时候处理不了太长的序列,这是因为把整个序列信息全部放在隐藏状态里面,当时间很长的话&#…

jmeter操作数据库

jmeter操作数据库 一、打开数据库 二、jmeter下载驱动,安装jdbc驱动 1、下载好的驱动包 2、将驱动包复制粘贴 存放在包的路径下 (1)jdk下面 a、路径:jdk1\jre\lib b、jdk1\jre\lib\ext (2)jmeter下 a、…

SpringIoC容器的初识

一、SpringIoC容器的介绍 Spring IoC 容器,负责实例化、配置和组装 bean(组件)。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。配置元数据以 XML、Java 注解或 Java 代码形式表现。它允许表达组成应用程序的组件以及这…

基于依赖注入技术的.net core WebApi框架创建实例

依赖注入(Dependency Injection, DI)是一种软件设计模式,用于实现控制反转(Inversion of Control, IoC)。在ASP.NET Core中,依赖注入是内置的核心功能之一。它允许你将应用程序的组件解耦和配置&#xff0c…

Linux:进程入门(进程与程序的区别,进程的标识符,fork函数创建多进程)

往期文章:《Linux:深入了解冯诺依曼结构与操作系统》 Linux:深入理解冯诺依曼结构与操作系统-CSDN博客 目录 1. 概念 2. 描述进程 3. 深入理解进程的本质 4. 进程PID 4.1 指令获取PID 4.2 geipid函数获取PID 4.3 kill指令终止进程 …

Linux驱动开发(速记版)--GPIO子系统

第105章 GPIO 入门 105.1 GPIO 引脚分布 RK3568 有 5 组 GPIO:GPIO0 到 GPIO4。 每组 GPIO 又以 A0 到 A7,B0 到 B7,C0 到C7,D0 到 D7,作为区分的编号。 所以 RK3568 上的 GPIO 是不是应该有 5*4*8160 个呢&#xff1…

MySQL高阶2004-职员招聘人数

目录 题目 准备数据 分析数据 实现 题目 一家公司想雇佣新员工。公司的工资预算是 70000 美元。公司的招聘标准是: 雇佣最多的高级员工。在雇佣最多的高级员工后,使用剩余预算雇佣最多的初级员工。 编写一个SQL查询,查找根据上述标准雇…

男单新老对决:林诗栋VS马龙,巅峰之战

听闻了那场激动人心的新老对决,不禁让人热血沸腾。在这场乒乓球的巅峰之战中,林诗栋与马龙的对决无疑是一场视觉与技术的盛宴。 3:3的决胜局,两位选手的每一次挥拍都充满了策略与智慧,他们的每一次得分都让人心跳加速。 林诗栋&am…

Linux自动化构建工具Make/Makefile

make是一个命令 makefile是一个文件 touch 创建并用vim打开makefile 写入依赖对象和依赖方法 mycode是目标文件 第二行数依赖方法 以tab键开头 make makefile原理 makefile中写的是依赖关系和依赖方法 clean英语清理文件 后不用加源文件。.PHONY定义clean是伪目标。 make只…

动态SLAM总结二

文章目录 Mapping the Static Parts of Dynamic Scenes from 3D LiDAR Point Clouds Exploiting Ground Segmentation:(2021)RF-LIO:(2022)RH-Map:(2023)Mapless Online …

[C++]使用纯opencv部署yolov11-pose姿态估计onnx模型

【算法介绍】 使用纯OpenCV部署YOLOv11-Pose姿态估计ONNX模型是一项具有挑战性的任务,因为YOLOv11通常是用PyTorch等深度学习框架实现的,而OpenCV本身并不直接支持加载和运行PyTorch模型。然而,可以通过一些间接的方法来实现这一目标&#x…

POLYGON Nature - Low Poly 3D Art by Synty 树木植物

一个低多边形资源包,包含可以添加到现有多边形风格游戏中的树木、植物、地形、岩石、道具和特效 FX 资源。 为 POLYGON 系列提供混合样式树这一新增功能。弥合 POLYGON 与更传统的层级资源之间的差距。还提供了一组经典的 POLYGON 风格的树木和植被以满足你的需求。 该包还附带…

系统安全 - Linux /Docker 安全模型及实践

文章目录 导图Linux安全Linux 安全模型用户层权限管理的细节多用户环境中的权限管理文件权限与目录权限 最小权限原则的应用Linux 系统中的认证、授权和审计机制认证机制授权机制审计机制 小结 内网安全Docker安全1. Docker 服务隔离机制Namespace 机制Capabilities 机制CGroup…