C语言----单链表的实现

前面向大家介绍了顺序表以及它的实现,今天我们再来向大家介绍链表中的单链表。

1.链表的概念和结构

1.1 链表的概念

链表是一种在物理结构上非连续,非顺序的一种存储结构。链表中的数据的逻辑结构是由链表中的指针链接起来的。

1.2 链表的结构

链表的结构与火车相似。

3e2efeadaa264c3b85afa3c54c291c68.jpeg

fec2696487ea4e3dba19cddc85e1a605.jpeg

火车是由一节一节的车厢构成的,并且各个车厢之间是相互独立的,且每个车厢都有属于自己的锁。假设火车上车厢的门都是锁上的状态,每个门都要对应的锁来开门,那我们如何快速的从第一个车厢走到最后一个车箱呢?

答案很简单,我们只要把下一节的车厢的钥匙放在上一节车厢就行了。

链表也是如此,车厢对应到链表中就是节点

所以链表是由一个个节点组成的,每个节点由存储的数据和指向下一个节点的指针组成的。

为什么需要指针呢?

因为链表在物理结构上是不连续的,由于连表中的节点的地址是由计算机随机分配的,我们并不能清楚的知道各个节点的具体位置,这时候就需要指针了。通过指针我们就能知道每个节点的位置。

53a3ff77d2f04c1f8606ab16adb7d5fc.jpeg

上图就是一个单链表的结构,plist是一个指向第一个节点的指针,往后看会发现,每一个节点都会包含了下一个节点的指针。

2.单链表的实现 

单链表的实现,我们依然通过三个文件来实现,为SList.h,SList.c和test.c

2.1 单链表的创建

我们通过前面顺序表就可以很快写出以下代码

typedef int SLDataType;
struct SListNode
{SLDataType data;struct SListNode* next;
}SList;

2.2 单链表的初始化

链表是由一个一个的节点组成的,所以我们要为节点申请空间,会用到malloc函数。

void test01()
{//手动初始化SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));node1->data = 1;SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));node2->data = 1;SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));node3->data = 1;SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));node4->data = 1;node1->next = node2;node2->next = node3;node3->next = node4;node4->next = NULL;SLTNode* plist = node1;
}

为了方便观察,我们先写一个打印链表的函数。

SLPrint(SLTNode* phead)
{SLTNode* pur = phead;while (pur){printf("%d->", pur->data);pur = pur->next;}printf("NULL\n");
}

解释以上代码

pur是一个指向第一个节点的指针,我们知道最后一个节点中的指针为NULL,pur在不断的变换为next的值,直到pur的值为NULL就跳出循环。

f0d6b35670ad48bda8dd6cf097971e35.png

运行代码

714f26bb7f334b37a28066987501ba6a.png

我们就发现单链表初始化成功了。

但是上面的代码是我们动手来实现链表的初始化的,但这样写代码的效率就会降低,我们一般都会通过函数来实现,这就涉及到了链表数据的插入。

2.3 数据的插入

数据的插入方式我们分为尾插和头插的两种。

2.3.1 尾插

尾插,顾名思义就是从链表中的尾部插入一个新的节点。

c5cf0898e9de45b083801c6157360b55.png

上图就是尾插的形式。

思路分析

既然我们要插入一个新的节点,我们就要为新的节点申请空间,为了方便,我们同样把申请空间的操作包装成一个函数。

//申请空间
SLTNode* SLBuySpace(SLDataType x)
{SLTNode* new = (SLTNode*)malloc(sizeof(SLTNode));//判断空间是否申请成功if (new == NULL){perror("malloc fail");exit(1);//退出程序}//到这空间申请成功new->data = x;new->next = NULL;return new;
}

接着,既然要实现尾插,我们就要找到链表的尾巴,然后才能插上新的节点。

//找尾巴
SLTNode* ptail = *pphead;
while (ptail->next)
{ptail = ptail->next;
}
ptail->next = newnode;

尾插的总代码

//尾插
void SLPushBack(SLTNode** pphead, SLDataType x)
{assert(pphead);SLTNode* newnode = SLBuySpace(x);if (*pphead == NULL){//链表为空*pphead = newnode;}else{//链表不为空//找尾巴SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}ptail->next = newnode;}}

需要注意的是,我们这里的形参是一个二级指针,因为我们要将原来phead指针指向的内容进行改变,如果我们单独将指针的值传过来,通过前面的学习,我们传值时,形参的改变是不会影响实参的,所以我们要将指针的地址传过来,通过地址修改实参的值。 

13f8f7097aaa4d2ab67af3c839aec2dc.png

运行代码

fc28d4e48bfc40d492402fa3b782493e.png

还需注意的是,我们要将链表为空和不为空分为两种情况处理,如果我们只考虑到链表不为空的情况,则当我们一开始处理的链表为空时,就不会进入循环,为空,ptial->next就无法进行解引用。 

2.3.1 头插 

头插,也就是将一个新的节点插入链表的头部,使新的节点称为第一个节点。

473e5bd44d8647b2b9bf9f5aad03b5b9.png

代码实现

//头插
void SLPushHead(SLTNode** pphead, SLDataType x)
{assert(pphead);//为新节点申请空间SLTNode* newnode = SLBuySpace(x);newnode->next = *pphead;*pphead = newnode;
}

头插的代码很简单,但是最后要让newnode成为新的phead,不要漏掉*pphead=newnode。

运行代码

b159301993f147448d07d6ae5585bdd4.png

2.4 数据的删除

2.4.1  尾删

尾删就是将链表中的最后一个节点删除掉。

思路分析

尾删我们就要找到链表的尾巴,并将其释放掉,但注意的是,当我们将最后一个节点释放掉之后,前一个节点中的next就会变成野指针,所以我们也要找到最后一个节点的前一个节点,并将其next指针赋值为NULL。

尾删前如下图

ffdee85191284d88ae196b6577c89b21.png

尾删后如下图

fbcd01006e1d4f818cae9ada4764556b.png

代码实现

void SLPopBack(SLTNode** pphead)
{assert(pphead && *pphead);//链表不能为空SLTNode* prev = *pphead;SLTNode* ptail = *pphead;while (ptail->next){prev = ptail;ptail = ptail->next;}//这里,prev和ptail找到free(ptail);prev->next = NULL;
}

运行代码

de8015dcc63d4089be289389215dc4e3.png

2.4.2 头删

 头删就是将链表中的第一个节点删点。

代码实现

//头删
void SLPopHead(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;//将下个节点变为新的节点
}

我们需要把下一个节点变为新的头节点,所以我们创建一个next先将下一个节点的地址存储起来。 

2.5 查找数据

查找数据很简单,只需遍历链表,并返回存储要查询数据节点的地址,如没由,就返回NULL。

代码实现

SLTNode* SLDataFind(SLTNode* phead, SLDataType x)
{assert(phead);SLTNode* pcur = phead;//遍历链表while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}

运行代码

e83a55ba8c2847a7a1ae0eaf47da26db.png

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

在指定位置之前插入数据,会影响到插入数据前一个节点的·指针,所以我们要找到插入位置的前一个节点。如下图

90db917b4b7e4ad39e02d43506eab8ea.png

代码实现

void SLAddPos(SLTNode** pphead, SLTNode* pos, SLDataType x)
{assert(pphead);assert(pos);SLTNode* newnode = SLBuySpace(x);if (*pphead == pos){//链表为空//头插SLPushHead(pphead, x);}else{//链表不为空//找位置pos前面的节点SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}newnode->next = pos;prev->next = newnode;}	
}

2.7 在指定位置之后插入数据 

 在指定位置之后插入数据就很简单,这个操作会影响插入位置的后一个指针,因为我们可以通过插入位置来找到插入位置的后一个节点,不在需要遍历链表。

0f08b6b2f0f24a11892814e10541bfa0.png

我们只需将pos->next指向newnode,让newnode->next指向pos->next。

代码实现

void SLAddBack(SLTNode* pos, SLDataType x)
{assert(pos);//为插入节点申请空间SLTNode* newnode = SLBuySpace(x);SLTNode* next = pos->next;pos->next = newnode;newnode->next = next;
}

这里我们要先将pos->next保存下来,因为后面的pos->next会发生改变,而我们要让newnode->next指向原来的pos->next;

2.8 删除pos节点的数据 

当我们删除pos节点时,会影响到pos前后两个节点的指针,所以,我们首先要找到pos前后的两个节点。

4a2347ffd63c4bb682748191037eb922.png

代码实现

void SLErasePos(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);assert(pos);if (pos == *pphead){//只有一个节点//头删SLPopHead(pphead);}else{SLTNode* next = pos->next;//找prevSLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}free(pos);pos = NULL;prev->next = next;}}

注意事项:我们要先将pos->next的值先存储起来,因为前面的pos就会被释放掉了,最后就找不到pos->next了。

我们还要分情况讨论,当链表中只有一个节点时,那就是头删操作了,直接调用头删的函数就行了。

运行代码

7afd31b9624149cdab671233180cecb6.png

2.9 删除pos后的节点

思路分析

既然要删除pos后的节点,首先链表就不能为空,pos->next也不能为空。

0e995b8904064e49a68264cabefe57c6.png

注意,将pos后面的节点释放掉了之后,此时pos->next就是野指针来,要注意将pos->next置为空。

代码实现

void SLEraseAfter(SLTNode* pos) //pos->data==1
{assert(pos && pos->next);//要删除的节点SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

d309232e88b347fea5087b5a01da7435.png

2.10 销毁链表

销毁链表就一个一个销毁就行了。

代码实现

void SLBreak(SLTNode** pphead)
{SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

感谢观看。 

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

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

相关文章

Cocos Creator 声音管理模块SoundMgr详解

前言 Cocos Creator 是一款用于开发2D和3D游戏的跨平台游戏引擎,它提供了丰富的功能和工具,使开发者能够快速开发出高质量的游戏。在游戏开发中,声音是一个非常重要的元素,可以增强游戏的氛围和互动性。为了更好地管理游戏中的声…

4、Flink执行模式(流/批)详解(下)

1、执行模式设置 import org.apache.flink.api.common.RuntimeExecutionMode; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;/*** bin/flink run -Dexecution.runtime-modeBATCH <jarFile>*/ public class _01_RuntimeMode {public s…

ROM修改进阶教程------如何去除安卓机型系统的开机向导 几种操作步骤解析

在和很多工作室定制化系统中。手机在第一次启动的时候系统都会进入设置向导,虽然可以设置手机的基本配置。但有很多客户需要去除手机的开机向导来缩短开机时间。确保手机直接进入工作状态。那么今天的教程针去除对开机向导的几种方法做个解析。机型很多版本不同。操作也有不同…

配置jupyter的启动路径

jupyter的安装参考&#xff1a;python环境安装jupyter-CSDN博客 1&#xff0c;背景 继上一篇python环境安装jupyter&#xff0c;里面有一个问题&#xff0c;就是启动jupyter&#xff08;命令jupyter notebook&#xff09;之后&#xff0c;页面默认显示的是启动时候的路径。 …

AI 边缘计算平台 - 嘉楠堪智 CanMV K230 开箱

CanMV-K230 开发板采用的是嘉楠科技 Kendryte 系列 AIoT 芯片中的最新一代 SoC 芯片 K230。该芯片采用全新的多异构单元加速计算架构&#xff0c;最新高性能 RISC-V CPU 内置双核玄铁 C908 CPU, 主频高达 1.6GHz&#xff1b;是全球首款支持 RISC-V Vector 1.0 标准的商用 SoC&a…

python中如何用matplotlib写饼图

#代码 import matplotlib.pyplot as plt# 设置绘图的主题风格 plt.style.use(ggplot) # 中文乱码和坐标轴负号的处理 plt.rcParams[font.sans-serif][SimHei] plt.rcParams[axes.unicode_minus]False plt.rcParams[figure.figsize][10,8] # 构造数据 x [0.2515,0.3724,0.3336…

靠这套 Pytest 接口自动化测试框架,击败了99%的人

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 Pytest 的下载安装 1、Python3 使用 pip install -U pytest 安装 2、查看 pytest 版本信息 py…

keytool证书工具详解(二)

JDK自带的keytool证书工具详解 一、生成证书 keytool -genkey -alias tomcat -keyalg RSA -keystore D:/tomcat.keystore -keypass 123456 -storepass 123456 -dname "CN=xingming,OU=danwei,O=zuzhi,L=shi,ST=sheng,C=CN" keytool -genkey -alias tomcat -keyalg …

七天速记前端八股文(重点)

for in和正常for循环的区别 在遍历数组时&#xff0c;正常的 for 循环通常比 for...in 循环更适合。虽然 for...in 循环可以用于遍历数组&#xff0c;但它有一些潜在的问题和限制。 下面是一些使用 for 循环相对于 for...in 循环的优势&#xff1a; 顺序遍历&#xff1a;for…

【nodejs状态库mobx之computed规则】

The above example nicely demonstrates the benefits of a computed value, it acts as a caching point. Even though we change the amount, and this will trigger the total to recompute, it won’t trigger the autorun, as total will detect its output hasn’t been …

泛微E9开发 如何自定义流程标题

1、功能背景 主表中有“选择类别”下拉框字段&#xff0c;用户可以根据需求来选择申请类别&#xff0c;一般多个相似流程的申请可以合并成一个&#xff0c;但是为了区分&#xff0c;我们可以通过将标题修改的方式来使整个显示页面更明确。 2、展示效果 3、实现方法 注意&…

吴恩达深度学习笔记:深度学习的 实践层面 (Practical aspects of Deep Learning)1.6-1.8

目录 第一门课&#xff1a;第二门课 改善深层神经网络&#xff1a;超参数调试、正 则 化 以 及 优 化 (Improving Deep Neural Networks:Hyperparameter tuning, Regularization and Optimization)第一周&#xff1a;深度学习的 实践层面 (Practical aspects of Deep Learning)…

SpringCloud使用Nginx代理、Gateway网关以后如何获取用户的真实ip

前言 本文转载自: www.microblog.store,且已获得授权. 一、需求背景 微服务架构使用了Nginx代理转发、并且使用了SpringCloud的Gateway统一控制所有请求&#xff0c;现在有个需求&#xff1a; 做一个日子记录切面&#xff0c;需要记录用户请求的ip地址。 在上述双重背景下…

knife4j 空指针异常

knife4j 空指针异常 一开始正常访问&#xff0c;但是改着改着&#xff0c;就无法访问了&#xff0c;百度了一圈没找到原因&#xff0c;最后对比了之前版本的区别发现这里有问题。最后把这个注解去掉就好了。 只是我本人遇到的问题是这样的&#xff0c;仅供参考

C++对象的初始化和处理

生活中我们买的电子产品都基本会有出厂设置!在某一天我们不用时候也会删除一些自己信息数据保证安全。 C中的面向对象来源于生活&#xff0c;每个对象也都会有初始设置以及对象销毁前的清理数据的设置。 构造函数和析构函数 对象的初始化和清理也是两个非常重要的安全问题 一…

Android Studio布局

文章目录 LinearLayout线性布局排列方向排列位置行列权重 LinearLayout线性布局 从行开始&#xff0c;顶格 排列方向 android:orientation“horizontal”android:orientation“vertical”排列位置 注意layout_width和layout_height的值是match_parent还是wrap_content&…

人工智能路径规划算法:迭代加深搜索

迭代加深搜索&#xff08;Iterative Deepening Search, IDS&#xff09;是一种结合了广度优先搜索&#xff08;BFS&#xff09;和深度优先搜索&#xff08;DFS&#xff09;的搜索策略&#xff0c;它通过重复执行深度限制的深度优先搜索来实现。每次迭代&#xff0c;深度限制增加…

03_Scala变量和数据类型

文章目录 [toc] **变量和数据类型****1.注释****2.变量和常量****3. 标识符的命名规范****4.scala的字符串****5.键盘输入****5.1 StdIn.readLine()****5.2 从文件中读取数据****5.3 Scala向外写数据** 变量和数据类型 1.注释 和Java完全一样 ** ** 2.变量和常量 var name…

如何在Pycharm中使用Git来进行版本管理

推荐视频:git pycharm的使用 连接github_哔哩哔哩_bilibilipycharm git的使用简单介绍 最近应该不会更新技能相关视频了 准备开题, 视频播放量 13042、弹幕量 2、点赞数 208、投硬币枚数 143、收藏人数 343、转发人数 58, 视频作者 呃呃燕, 作者简介 努力入门的计算机双非研究生…

Spark面试整理-解释Spark中的广播变量和累加器

在Apache Spark中,广播变量(Broadcast Variables)和累加器(Accumulators)是两种特殊的共享变量,它们用于不同的用途并有助于优化分布式计算的性能和资源利用。 广播变量(Broadcast Variables) 广播变量用于在所有节点之间高效地分发大数据集,主要用于只读操作。当你有…