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,一经查实,立即删除!

相关文章

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

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

配置jupyter的启动路径

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

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

CanMV-K230 开发板采用的是嘉楠科技 Kendryte 系列 AIoT 芯片中的最新一代 SoC 芯片 K230。该芯片采用全新的多异构单元加速计算架构,最新高性能 RISC-V CPU 内置双核玄铁 C908 CPU, 主频高达 1.6GHz;是全球首款支持 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%的人

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

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

for in和正常for循环的区别 在遍历数组时,正常的 for 循环通常比 for...in 循环更适合。虽然 for...in 循环可以用于遍历数组,但它有一些潜在的问题和限制。 下面是一些使用 for 循环相对于 for...in 循环的优势: 顺序遍历: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、功能背景 主表中有“选择类别”下拉框字段,用户可以根据需求来选择申请类别,一般多个相似流程的申请可以合并成一个,但是为了区分,我们可以通过将标题修改的方式来使整个显示页面更明确。 2、展示效果 3、实现方法 注意&…

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

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

knife4j 空指针异常

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

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

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

Android Studio布局

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

03_Scala变量和数据类型

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

Shell脚本学习记录

0.理解Linux文件权限 0.1 Linux安全性 用户的权限是通过创建用户时分配的用户ID(UID)来追踪的,UID是个数值,每个用户都有一个唯一的UID 0.1.1 /etc/passwd文件 Linux系统使用一个专门的文件/etc/passwd来匹配登录名与对应的UID值,该文件包…

力扣37题:回溯算法之解数独

编写一个程序,通过填充空格来解决数独问题。 数独的解法需 遵循如下规则: 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图) 数独部分空…

R基本的数据管理

一&#xff0c;创建变量 创建一个数据框 > myData<-data.frame(x1c(1,2,3,4,5,6),x2c(6,5,67,8,9,0)) > myDatax1 x2 1 1 6 2 2 5 3 3 67 4 4 8 5 5 9 6 6 0增加一列为两者的和 > myData$sum<-myData$x1myData$x2 > myDatax1 x2 sum 1 1 6 …

Python浅谈清朝秋海棠叶版图

1、清朝疆域概述&#xff1a; 清朝是我国最后一个封建王朝&#xff0c;其始于1616年建州女真部努尔哈赤建立后金&#xff0c;此后统一女真各部、东北地区。后又降服漠南蒙古&#xff0c;1644年入关打败农民起义军、灭南明&#xff0c;削三藩&#xff0c;复台湾。后又收外蒙&am…

Visual Studio 2022 Professional、Enterprise安装教程

Visual Studio 2022 Professional、Enterprise安装教程 下载安装包安装 我是电脑已经有VS2019&#xff0c;现在加装一个VS2022。 下载安装包 首先下载安装包&#xff0c;进入官网进行下载&#xff0c;VS官网下载地址。 进入之后&#xff0c;会显示如下界面&#xff0c;选择Pro…

安卓NetworkStatsManager使用及demo

目录 一、TrafficStats类简介二、demo示例 一、TrafficStats类简介 TrafficStats Android API 8提供了android.net.TrafficStats类。 通过此类能获取设备重启以来网络信息&#xff0c;部分函数如下所示&#xff1a; static long getMobileRxBytes() //获取通过移动数据网络…

开放式激光振镜运动控制器的视觉校正振镜精度解决方案

市场应用背景 激光振镜控制系统因具有惯量小、低负载、响应速度极快等优点&#xff0c;非常适合高速微加工应用&#xff0c;如激光标刻、焊接、3D打印和精密切割等应用。 激光振镜控制系统主要涵盖了激光振镜控制和图形校正等两个技术层面&#xff0c;来共同控制激光在加工过…