数据结构初阶-单链表

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

        而我们主要要熟悉的单链表与双向链表的全称分别为:不带头单向不循环链表,带头双向循环链表,当我们对这两种链表熟悉后,便能不费力的推出另外四种链表。我们本文主要介绍单链表。

 一,单链表的概念与结构

1.1概念       

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

        这里我们可以联想平时我们所坐的高铁或火车,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉/ 加上,不会影响其他车厢,每节车厢都是独立存在的。

        那,在链表中我们的火车车厢又是怎么样的:

 1.2结点

        与顺序表不同的是,链表里的每节"车厢"都是独立申请下来的空间,我们称之为“结点/结点”。结点的组成主要有两个部分:当前结点要保存的数据和保存下⼀个结点的地址(指针变量)。

        图中指针变量 plist保存的是第⼀个结点的地址,我们称plist此时“指向”第⼀个结点,如果我们希望 plist“指向”第⼆个结点时,只需要修改plist保存的内容为0x00B0。

1.3链表的性质

1、链式机构在逻辑上是连续的,在物理结构上不⼀定连续

2、结点⼀般是从堆上申请的

3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续

        结合前⾯学到的结构体知识,我们可以给出每个结点对应的结构体代码:(因为我们存储的数据类型不定,所以我们可以先将我们要存储的数据类型先定义下):

typedef int SLDataType;typedef struct SLTistNode
{SLDataType Data;struct SLTistNode* next;
}SLTNode,*PSLTNode;

        当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数据,也需要保存下⼀个结点的地址(当下⼀个结点为空时保存的地址为空)。

接下来让我们来实现单链表的常用功能,实现的功能大多与顺序表类似:

二,单链表功能的实现

2.1链表的初始化

        在介绍它的初始化之前,我们先来介绍什么是带头(哨兵位)和不带头,对于带头,则是链表头部初始时便有一个哨兵位节点,但不存放任何数据,由于我们的单链表为不带头链表,所以我们这里初始化直接引用上面的结构体,设置我们的链表头为空指针即可:

PSLTNode plist = NULL;

2.2获取新节点函数SLTBuyNode

       在对链表进行头尾插之前,由于我们每次插入节点都需要创建新的节点,所以我们则合理直接分装一个函数,后面实现头尾插功能直接调用即可,老样子,我们先给出代码:

PSLTNode SLTBuyNode(SLDataType x)
{PSLTNode node = (PSLTNode)malloc(sizeof(SLTNode));if (node == NULL){perror("newnode:");exit(1);}node->Data = x;node->next = NULL;return node;
}

       由于我们的链表内存的大小是动态变化的,所以我们需要使用malloc函数来开辟节点的空间。为了确保我们代码的健壮性,我们需要检验空间开辟是否成功。然后将所需要插入的数据赋值给新节点的data,同时将新节点的next置为空。

2.3尾插函数SLTPushBack

       由于我们这里是在链表的尾端进行尾插,所以我们直接将当前的链表尾节点的指针设置为我们要创建的新节点的地址:

void SLTPushBack(PSLTNode* pphead, SLDataType x)
{assert(pphead);PSLTNode newcode = SLTBuyNode(x);if (*pphead == NULL){*pphead = newcode;}else{PSLTNode pcur = *pphead;while (pcur->next){pcur = pcur->next;}pcur->next = newcode;}
}

        由于我们初次插入节点时,链表此时为空,所以我们在其为空时直接将指向新节点的指针设置为指向链表头节点的指针,但尾插我们需要遍历链表找到尾节点,所以时间复杂度为O(N)。

2.4头插函数SLTPushFront

       相对于尾插法,我们的头插法就简单许多,只需要将要插入的新节点的next指针直接设置为指向链表头节点的指针,然后再将头节点的指针设置为指向新节点的指针即可:

void SLTPushFront(PSLTNode* pphead, SLDataType x)
{assert(pphead);PSLTNode newcode = SLTBuyNode(x);if (*pphead == NULL){*pphead = newcode;}else{newcode->next = *pphead;*pphead = newcode;}
}

  我们可以明显的看到,头插法的方式没有遍历数组,它的时间复杂度为O(1)。

2.5尾删函数SLTPopBack 

       在进行尾删之前,我们需要判断链表中是否还有节点,如果没有节点,我们就不能再对链表进行删除:

void SLTPopBack(PSLTNode* pphead)
{assert(pphead && *pphead);PSLTNode pcur = *pphead;PSLTNode pcur1 = NULL;while (pcur->next){pcur1 = pcur;pcur = pcur->next;}if (pcur1){pcur1->next = NULL;free(pcur);pcur = NULL;}else{free(*pphead);*pphead = NULL;}
}

        首先我们要遍历链表找到尾节点,我们可以先对头节点的NEXT判空,如果为空,则说明链表中的节点只有一个,我们直接释放头节点即可。如果不为空,我们则直接循环找到尾节点,对其进行释放,同时将其上一个节点的NEXT置为空。

2.6头删函数SLTPopFront

         老样子,我们需要先判断链表是否还有节点,有直接先记住头节点的指针,然后让头指针指向它的NEXT即可:

void SLTPopFront(PSLTNode* pphead)
{assert(pphead && *pphead);PSLTNode pcur = *pphead;*pphead = pcur->next;free(pcur);pcur = NULL;
}

       我们在这里会发现,头删相比于尾删也简单很多,所以当我们在没有特殊要求的情况下,尽量使用头删/插对链表进行增删操作,这样复杂度会少很多。

2.7寻找指定节点函数SLTFind

       在实现删除/增加指定位置数据之前,由于这两个函数都需要寻找数据,所以我们可以直接将寻找函数分装为一个函数,便于我们后面直接调用:

PSLTNode SLTFind(PSLTNode phead, SLDataType x)
{assert(phead);PSLTNode pcur = phead;while (pcur){if (pcur->Data == x){return pcur;}pcur = pcur->next;}return NULL;
}

       如果没有找到目标数据,我们直接返回空指针即可,同时我们需要遍历链表一个一个去寻找,这是我们常用的寻找方法。

2.8向指定位置之后插入数据函数SLTInsertAfter

       我们这里不介绍在之前插入数据的原因是因为此时我们需要遍历链表去找到指定位置前方的数据,所以复杂度会高很多,而这里我们删除指定位置之后则不需要遍历链表:

void SLTInsertAfter(PSLTNode pos, SLDataType x)
{assert(pos);PSLTNode newcode = SLTBuyNode(x);newcode->next = pos->next;pos->next = newcode;
}

需要注意的即是对NEXT指针指向的改变,实现难度不高。

2.9删除指定位置之后的数据函数SLTEraseAfter

 与之前的删除一样,我们需要先判断链表中是否有节点:

void SLTEraseAfter(PSLTNode pos)
{assert(pos && pos->next);PSLTNode del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}

  会了我们上面的头删与尾删函数之后,其实这部分很简单。

2.10链表的销毁函数SListDestroy 

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

这里我们也需要判断链表是否为空,如果为空我们也就没必要释放链表空间 。

三,几道单链表OJ题推荐

203. 移除链表元素 - 力扣(LeetCode)

206. 反转链表 - 力扣(LeetCode)

876. 链表的中间结点 - 力扣(LeetCode)

21. 合并两个有序链表 - 力扣(LeetCode)

链表分割_牛客题霸_牛客网 (nowcoder.com)

链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

160. 相交链表 - 力扣(LeetCode)

141. 环形链表 - 力扣(LeetCode)

142. 环形链表 II - 力扣(LeetCode)

       这里尤其是最后两道,使用了Floyd龟兔赛跑算法,非常推荐去练习学习 。

       

 

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

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

相关文章

重生之我们在ES顶端相遇第5章-常用字段类型

思维导图 前置 在第4章,我们提到了 keyword(一笔带过)。在本章,我们将介绍 ES 的字段类型。全面的带大家了解 ES 各个字段类型的使用场景。 字段类型 ES 支持以下字段类型(仅介绍开发中常用,更多内容请自…

AI App Store-AI用户评价-多维度打分对比pk-AI社区

C端用户、创作者、AI达人们在选择众多国内外AI厂商的服务时候往往感到一头雾水,那么多功能接近的AI应用(智能对话类、文档总结类、文生图、AI搜索引擎) 究竟在不同用户需求场景下表现怎么样。大部分人如果有需求都会所有平台都尝试一遍,比如一个博主生成…

C++自定义字典树结构

代码 #include <iostream> using namespace std;class TrieNode { public:char data;TrieNode* children[26];bool isTerminal;TrieNode(char ch){data ch;for (int i 0; i < 26; i){children[i] NULL;}isTerminal false;} }; class Trie { public:TrieNode* ro…

基于区块链技术的中药饮片代煎配送服务与监管平台

业务背景 近年来&#xff0c;随着公众对中医药青睐有加&#xff0c;中药代煎服务作为中医药现代化的重要一环&#xff0c;在全国各地蓬勃兴起。鉴于传统煎煮方式的繁琐耗时&#xff0c;医疗机构纷纷转向与第三方中药饮片企业合作&#xff0c;采用集中代煎模式。这些第三方煎药中…

Proactor模型

文章目录 概述1. 异步I/O操作2. 事件通知3. 事件处理函数4. 事件循环5. 多线程支持6. 非阻塞I/O7. 可扩展性8. 错误处理9. 资源管理10. 编程复杂性11. 应用场景流程图 结论 概述 Proactor模型是一种基于异步I/O操作的事件驱动编程模型&#xff0c;主要用于处理并发的I/O事件&a…

冒泡排序(数组作为函数参数)

什么是冒泡排序&#xff1f; 冒泡排序&#xff08;Bubble Sort&#xff09;也是一种简单直观的排序算法。它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换&#xff0c;…

docker--容器数据进行持久化存储的三种方式

文章目录 为什么Docker容器需要使用持久化存储1.什么是Docker容器&#xff1f;2.什么是持久化存储&#xff1f;3.为什么Docker容器需要持久化存储&#xff1f;4.Docker如何实现持久化存储&#xff1f;(1)、Docker卷(Volumes)简介适用环境:使用场景:使用案例: (2)、绑定挂载&…

pycharm+pytorch2.3.1安装

成功运行 Anaconda简介 Anaconda 就是可以便捷获取包且对包能够进行管理&#xff0c;同时对环境可以统一管理的发行版本。Anaconda包含了conda、Python在内的超过180个科学包及其依赖项。 Anaconda安装 去官网地址下载 Download Anaconda Distribution | Anaconda​www.ana…

SpringBoot启动原理详解

透彻理解SpringBoot启动原理&#xff08;一&#xff09; 一张Spring启动顺序图我们对Spring启动原理有多少理解呢一起看一下Spring有那些扩展点和启动过程有关通过打印日志学习Spring的执行顺序实例化和初始化的区别Spring重要扩展点的启动顺序1.BeanFactoryPostProcessor2.实例…

python3.10.4——Windows环境安装

python下载官网&#xff1a;https://www.python.org/downloads/ 如果安装在C盘&#xff0c;需要右键→选择“以管理员身份运行” 勾选2个按钮&#xff0c;选择自定义安装 全部选择&#xff0c;点击Next 更改安装路径 命令行检查python是否安装成功&#xff1a; 出现版本号说明…

内存泄漏详解

文章目录 什么是内存泄漏内存泄漏的原因排查及解决内存泄漏避免内存泄漏及时释放资源设置合理的变量作用域及时清理不需要的对象避免无限增长避免内部类持有外部类引用使用弱引用 什么是内存泄漏 内存泄漏是指不使用的对象持续占有内存使得内存得不到释放&#xff0c;从而造成…

【Hot100】LeetCode—416. 分割等和子集

目录 题目1- 思路2- 实现⭐152. 乘积最大子数组——题解思路 3- ACM 实现 题目 原题连接&#xff1a;416. 分割等和子集 1- 思路 理解为背包问题 思路&#xff1a; 能否将均分的子集理解为一个背包&#xff0c;比如对于 [1,5,11,5]&#xff0c;判断能否凑齐背包为 11 的容量…

面试场景题系列--(1)如果系统的 QPS 突然提升 10 倍该怎么设计?--xunznux

1. 如果系统的 QPS 突然提升 10 倍该怎么设计&#xff1f; 1.1 硬件的扩展微服务的拆分 如果所有的业务包括交易系统、会员信息、库存、商品等等都夹杂在一起&#xff0c;当流量一旦起来之后&#xff0c;单体架构的问题就暴露出来了&#xff0c;机器挂了所有的业务就全部无法…

SSCI 二区正刊 绿色金融、财政、经济、债务、成本、创新题目:

1金融科技能提升企业的双元创新能力吗&#xff1f;组织韧性xxxxx 2从财政分权到经济高质量发展&#xff1a;税收征管强度xxxxxxx 3企业智能化转型、债务融资成本与绿色xxxx 绿色金融改革能否促进地方经济高质量发展&#xff1a;基于绿色金融改革创新试验区的准xxxx 4绿色金融改…

MBR60200PT-ASEMI无人机专用MBR60200PT

编辑&#xff1a;ll MBR60200PT-ASEMI无人机专用MBR60200PT 型号&#xff1a;MBR60200PT 品牌&#xff1a;ASEMI 封装&#xff1a;TO-247 批号&#xff1a;最新 恢复时间&#xff1a;35ns 最大平均正向电流&#xff08;IF&#xff09;&#xff1a;60A 最大循环峰值反向…

win11 安装 Gradle

一、win11 安装Gradle(7.5.1)&#xff1a; 1.1、下载二进制包 Gradle下载页面 1.2、配置环境变量 变量名&#xff1a;GRADLE_HOME 变量值&#xff08;二进制包解压路径&#xff09;&#xff1a;D:\develop-tool\gradle-7.5.1 变量名&#xff1a;GRADLE_USER_HOME 变量值&a…

JAVA基础 - 控制语句

目录 一. 简介 二. 分支语句 三. 循环语句 四. 跳转语句 一. 简介 在 Java 中&#xff0c;控制语句用于控制程序的执行流程&#xff0c;根据不同的条件决定执行哪些代码块。常见的控制语句包括&#xff1a; if-else 语句&#xff1a;根据条件的真假执行不同的代码块。 swi…

Spark实时(一):StructuredStreaming 介绍

文章目录 StructuredStreaming 介绍 一、SparkStreaming实时数据处理痛点 1、复杂的编程模式 2、SparkStreaming处理实时数据只支持Processing Time 3、微批处理&#xff0c;延迟高 4、精准消费一次问题 二、StructuredStreaming概述 三、​​​​​​​​​​​​​​…

BGP选路之AS-PATH

原理概述 当一台BGP路由器中存在多条去往同一目标网络的BGP路由时&#xff0c;BGP协议会对这些BGP路由的属性进行比较&#xff0c;以确定去往该目标网络的最优BGP路由。首先要比较的属性是 Preferred Value&#xff0c;然后是Local Preference&#xff0c;再次是路由生成方式&a…

算法学习笔记:回溯法

回溯法有“通用的解题法”之称。用它可以系统地搜索一个问题的所有解或任一解。回溯法是一个既带有系统性又带有跳跃性的搜索算法。它在包含问题的所有解的解空间树中&#xff0c;按照深度优先的策略&#xff0c;从根节点出发搜索解空间树。算法搜索至解空间树的任一节点时&…