单链表讲解

一.链表的概念以及结构

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

链表的结构与火车是类似的,一节一节的,数据就像乘客一样在车厢中一样。

与顺序表不同的是,链表里的每节"车厢"都是独立申请的空间,我们将这样的空间称为节点或者结点

 既然链表是一节一节的结点构成的,那么这样的结点是怎么连接的呢?

链表中的节点一般是通过结构体来实现的,结构体中的存储着数据和下一个节点的地址,这样我们就可以访问下一个节点中的数据了。

节点:

typedef struct SListNode
{SLTDataType val;struct SListNode* next;
}SLTNode;

为了使用方便我们可以对其重命名。

二.单链表的实现

typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data; //节点数据struct SListNode* next; //指针保存下⼀个节点的地址
}SLTNode;
void SLTPrint(SLTNode* phead);
//头部插⼊删除/尾部插⼊删除
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);

首先我们来思考如何打印数据呢?

void SLTPrint(SLTNode* phead);

这个参数是指向第一个头节点的指针,为什么要使用指针呢?

通过指针我们可以访问这个结构体中的元素,如果我们不使用指针,形参是实参的一份临时拷贝,如果数据过大,这样会很浪费空间(我们这里存储的数据是整形,为了泛用性,我们还是要使用一级指针),直接使用会降低程序性能。

phead->data就可以访问到数据了,那么下一个节点的数据呢?

这时候就会用到我们在结构体中存储的指针了,这个指针指向下一个结构体,所以再通过下一个结构体我们就可以访问下一个结构体的数据,同理,下下个结构体的指针同理。

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

通常习惯上,我们是不会改变直接传过来的参数的,所以我们要使用的话,就再使用一个变量来进行接收。

这里pcur不为NULL时,代表我们的指针并没有走到结构体的末尾,我们就可以以它作为限制条件,每经过一个节点,就打印一次数据,直到遇到空指针停止。 

然后就是要完成指定位置之前插入数据了。

假设有一个链表1->>2->>3->>5->>NULL.

我们要在数据5的前面插入4.可是在插入之前,我们要找到数据五的位置才能插入。

如何查找元素呢?

SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

第一个参数是指向第一个节点的指针,第二个是要寻找的数据,虽然这里可能会存在相同的数字,但是这只是我们练习的场景,假设我们要存储人的身份信息,这是不可能存在相同的人的,所以我们这里假设数据不相同。

最后返回这个节点的地址。

SLTNode* SLTFind(SLTNode* phead, SLTDataType x) {assert(phead);SLTNode* pcur = phead;while (pcur) {if (pcur->data == x) {return pcur;}pcur = pcur->next;}return NULL;
};

和打印数据相同。我们只需要遍历整个数组即可,如果找到就返回这个节点的地址,否则就返回空。

接着我们再来完成指定位置插入数据。

即使我们找打这个节点的地址,我们要在它之前插入数据,也就是再创建一个节点,然后把这个节点的next指针指向找的节点,这样我们就在指定节点前面插入了数据。

但是我们新建的这个节点是没有被节点指向的,所以我们还要让前一个节点指向新的节点。

这个SLTBuyNode是创建新节点函数,我们在插入数据时,会频繁用到这个代码,如果重复的写,就太浪费时间了,所以我们单独封装一个函数来完成这个功能。

SLTNode* SLTBuyNode(SLTDataType x) {SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL) {perror("malloc");exit(1);}newnode->data = x;newnode->next = NULL;return newnode;
}
		SLTNode* pcur = *pphead;while (pcur->next != pos) {pcur = pcur->next;}pcur->next = SLTBuyNode(x);pcur->next->next = pos;

可是这样写就完了吗?

如果我们要在链表的第一个节点之前插入数据,我们会发现这个代码循环是根本没有进入的,并且会访问NULL地址,这是非法的。

所以为了防止这样的问题,我们还需要额外区分头插的情况。

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {assert(pphead);assert(*pphead);if (*pphead == pos) {*pphead = SLTBuyNode(x);(*pphead)->next = pos;}else {SLTNode* pcur = *pphead;while (pcur->next != pos) {pcur = pcur->next;}pcur->next = SLTBuyNode(x);pcur->next->next = pos;}
};

 然后我们来思考,为什么这里要使用二级指针,我们使用指针的目的是为了在函数中改变指针所指向的变量的值,这里就是传值和传址的区别了,我们在头插的过程中指向头节点的指针,是会改变的,如果我们传一级指针,是改变不了这个指针的,所以我们要使用二级指针。

然后就是删除数据函数

void SLTErase(SLTNode** pphead, SLTNode* pos);

这里与插入函数一样,是需要考虑头删的。

void SLTErase(SLTNode** pphead, SLTNode* pos) {assert(pphead);assert(*pphead);SLTNode* pcur = *pphead;if (pos == *pphead) {*pphead = pos->next;free(pos);}else {while (pcur->next != pos) {pcur = pcur->next;}pcur->next = pos->next;free(pos);pos = NULL;}
};

删除指定节点,只需要我们将前一个节点指向下一个节点就行了,然后再释放指定节点即可 。

而头删就是释放头节点,然后将头指针指向头节点的下一个节点既可以。

为了方便,头插头删位插尾删,我们都使用前面两个函数来进行完成,这样更加方便。

void SLTPushBack(SLTNode** pphead, SLTDataType x) {assert(pphead);if (*pphead == NULL) {*pphead = SLTBuyNode(x);}else {SLTInsert(pphead,NULL,x);}
}

在尾插时,如果链表为空,我们就直接创建一个新的节点即可,否则我们就只需要在NULL前面插入一个节点即可,因为最后一个节点的next指针指向的是空。

void SLTPushFront(SLTNode** pphead, SLTDataType x) {assert(pphead);if (*pphead == NULL) {*pphead = SLTBuyNode(x);}else {SLTInsert(pphead, *pphead, x);}
};

头插同理,如果链表为空直接创建新的链表即可,否则就是在头节点之前插入即可,而且头节点直接作为参数给我们使用了,所以我们就不需要再查找了. 

void SLTPopBack(SLTNode** pphead) {assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur->next != NULL) {pcur = pcur->next;}SLTErase(pphead,pcur);
};

尾删就需要我们遍历链表,找到最后一个链表的指针 ,这样再删除即可。链表为空就不能删了,所以我们要断言一下。

void SLTPopFront(SLTNode** pphead) {assert(pphead && *pphead);SLTErase(pphead,*pphead);
};

头删更简单,不需要遍历,直接调用删除函数即可。

//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x) {assert(pos);SLTNode* next = SLTBuyNode(x);next->next = pos->next;pos->next = next;
};
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos) {assert(pos);SLTNode* next = pos->next;pos->next = next->next;free(next);next = NULL;
};

 这里是同理,要插入就直接创建新链表然后插入即可,删除也是同理,这里是需要判断pos是否为空的,因为如果没有找到find函数就会返回空。

删除链表

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

就需要遍历整个链表然后依次释放,最后为了防止野指针,我们还需要将*pphead置为NULL。 

三.链表 

链表的种类有很多种。

任意两两组合就有2 * 2 * 2 = 8种组合

这个带头和不带头后面双链表再讲解。

我们通常所说的单链表是不带头单向不循环链表。

单向就是通过一个只能访问下一个节点不能访问上一个节点,否则就是双向。

循环就是链表围成一个圈,链表的最后一个节点next指向头节点了,而不是NULL.

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

 

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

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

相关文章

如何使用pytorch进行图像分类

如何使用pytorch进行图像分类https://featurize.cn/notebooks/5a36fa40-490e-4664-bf98-aa5ad7b2fc2f

【软考】UML中的图之用例图

目录 1. 说明2. 建模2.1 说明2.2 语境建模2.3 需求建模 3. 图示4. 组成部分 1. 说明 1.用例图(Use Case Diagram)。2.展现了一组用例、参与者(Actor)以及它们之间的关系。3.用例图通常包括以下的内容:用例、参与者、用…

配置IP地址并验证连通性

1.实验环境 主机 A和主机 B通过一根网线相连,如图6.13所示。 图6.13 实验案例一示意图 2.需求描述 为两台主机配置!P地址,验证P地址是否生效;验证同一网段的两台主机可以互通,不同网段的主机不能直接互通。 3.推荐步骤 为两台…

uni原生导航栏相关设置

动态设置某一项内容 使用场景:不同角色显示不同导航栏或设置不同名称,不同图标 API: uni.setTabBarItem(OBJECT) 属性类型默认值必填说明indexnumber无是tabBar的哪一项,从左边算起(从0开始)textString无否tab上的按…

【Linux】封装一下简单库 理解文件系统

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、封装一下简单库 二、理解一下stdin(0)、stdout(1)、stderr(3) 2.1、为什么要有0、1、2呢? 2.2、特点 2.3、如果我想让2也和1重定向到一个文件…

uni-app 微信小程序设置全局转发给朋友、分享到朋友圈

小程序右上角原生菜单自带的分享按钮,默认不可用 1.创建一个mixin share.js export default {created() {//#ifdef MP-WEIXINwx.showShareMenu({withShareTicket: true,menus: [shareAppMessage, shareTimeline]});//#endif}, }export default {created() {//#ifde…

vs2022断点调试怎么看堆栈帧,找异常位置

打一个断点以后,会出现如图报错 我们要怎么找到报错的语句?鼠标点击->堆栈帧->上一行运行的位置->直到找到错误出错如图所示: 跳转到,我们手写的代码,执行出错的位置

Unity | Shader基础知识(第十二集:颜色混合)

目录 前言 一、日常生活中的常见现象 二、unity自带的一个结构体(表面着色器SurfaceOutputStandard) 三、自己写一个颜色混合的Shader 1.只加基础颜色Albedo 2.加入法线 3.加入光滑度 4.加入金属度 5.加入自发光 四、作者的话 前言 shader里每一…

docker安装nessus服务及使用

Nessus 是目前全世界最多人使用的系统漏洞扫描与分析软件,现在软件服务越来越多,越来越复杂,涉及的数据也更多;因此系统完成后对于系统漏洞的检测并对其进行修改十分有必要,本文介绍通过docker安装nessus服务及简单的使…

设计模式-模板方法模式(TemplateMethod)

1. 概念 模板方法模式是一种行为设计模式,它在一个方法中定义算法的骨架,将一些步骤延迟到子类中实现。 2. 原理结构图 2.1 图 2.2 角色 抽象类(Abstract Class) 定义抽象的基本操作(Primitive Operations&#xff…

从启发式到模型化 京东推荐广告排序机制演化

1、序言:广告排序机制的前世今生 1.1、简介:广告排序机制 在线广告是国内外各大互联网公司的重要收入来源之一,而在线广告与传统广告最大的区别就在于其超大规模的实时竞价环境:数以万计的广告主在一天内可以参与亿级别的流量竞…

解决宝塔的FTP无法使用被动模式

问题:宝塔安装完ftp管理软件之后,无法使用被动模式连接 解决: 提示: 如果还是不行,那么要看看防火墙和安全组有没有放行被动模式的端口,宝塔安装的pure-ftpd软件的被动模式端口默认是39000至400…

Kubernetes 升级不弃 Docker:KubeKey 的丝滑之道

作者:尹珉,KubeSphere Ambaasador&Contributor,KubeSphere 社区用户委员会杭州站站长。 引言 随着 Kubernetes 社区的不断发展,即将迎来 Kubernetes 1.30 版本的迭代。在早先的 1.24 版本中,社区作出一个重要决策…

计算机网络——42攻击和对策

攻击和对策 IDS:入侵检测系统 分组过滤 对TCP/IP头部进行检查不检查会话间的相关性 IDS:intrusion detection system 深入分组检查:检查分组的内容(e.g. 检查分组中的特征串,已知攻击数据库的病毒和攻击串)检查分组间…

【网站项目】捷邻小程序

🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板&#xff…

FPGA - 以太网UDP通信(二)

一,引言 前文链接:FPGA - 以太网UDP通信(一) 在上文章中介绍了以太网简介,以太网UDP通信硬件结构,以及PHY芯片RGMII接口-GMII接口转换逻辑,接下来介绍UDP通信结构框图以及数据链路层&#xff…

Python | Leetcode Python题解之第28题找出字符串中的第一个匹配项的下标

题目: 题解: class Solution:def strStr(self, haystack: str, needle: str) -> int:# Func: 计算偏移表def calShiftMat(st):dic {}for i in range(len(st)-1,-1,-1):if not dic.get(st[i]):dic[st[i]] len(st)-idic["ot"] len(st)1re…

自己开发的App如何上架,详细解读App上架操作流程

对于企业或个人开发的App,上架是必经之路。然而,许多人不清楚如何进行App上架。工信部在2023年规定,App必须备案才能上架。那么,让我们一起了解App上架流程吧。 1. 准备上架所需材料 在上架App之前,需要准备应用图标…

类加载子系统

目录 类的加载 加载流程 类的加载器 类的链接 类的检验阶段 类的准备阶段 类的解析阶段 类的初始化 static与final的搭配问题 ()的线程安全性 类的初始化情况:主动使用vs被动使用 类的使用 类的卸载 类、类的加载器、类的实例之间的引用关系 类的生命…

端口协议(爆破、未授权)

常见端口服务及攻击方向: 弱口令爆破 工具:https://github.com/vanhauser-thc/thc-hydra hydra是一个支持多协议的自动化的爆破工具。 支持的服务、协议: telnet ftp pop3[-ntlm] imap[-ntlm] smb smbnt http-{head|get} http-{get|post}-…