【数据结构】——双向链表

news2025/5/14 5:35:04

一、链表的分类

我们前面学习了单链表,其是我们链表中的其中一种,我们前面的单链表其实全称是单向无头不循环链表,我们的链表从三个维度进行分类,一共分为八种。

1、单向和双向

可以看到第一个链表,其只能找到其后一个节点,是没办法找到其前面的节点的,其遍历只能从左往右遍历。

而我们的双向链表,其节点中,有三个元素,一比我们单向的多了一个,其就多了一共指向前一共节点的指针,我们的双向链表是可以寻找到前一个节点的,其是可以从左往右遍历,也可以从右往左边进行遍历。

2、带头或不带头

前面我们将单链表的时候老是说头节点,其实我们前面的说法是有点不妥的,我们所说的头节点指的是链表的第一个节点。

而链表的带头是指其有一个节点,其是不存储数据的,就用来占位置的,其实前面我们在刷题的时候也使用过了的,就是在一些题目上,我们需要对是否为空链表进行判断,我们为了减少代码的冗余,我们会向操作系统申请一个空间,但是是不存储数据的,只是用来占位置的,这个节点我们也称为哨兵位。、

3、循环或不循环

前面我们学习的单链表就是一个不循环的链表,我们看上面的图就大概可以理解了,就是这个链表可以形成环,而且这个环是从第一个节点开始的。

虽然我们的链表有这么多种类型,不过实际上我们常用的就两种:

1、无头单向不循环链表:结构简单,一般不会单独用来存储数据。实际中更多是作为其他数据结构的子结构,例如:哈希表、图的邻接等。还有就是面试和笔试中常见。

2、带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。而且这个结构虽然复杂,但是其使用代码实现以后会发现其结构会有很多好处、其很多操作实现是很简单的,后续我们代码就知道了。

二、带头双向循环链表

1、概念与结构

从图中我们可以看到,这个链表中,首先第一个节点,其是不进行数据的存储的,然后其有两个指针,其中一个指针指向的是下一个节点,然后一个指针指向的是前一个节点,然后又因为其是循环的 所以其头节点的指向前一节点的指针指向的是尾节点,而尾节点的指向下一节点的指针指向的是头节点。

下面我们来实现一个双向带头循环链表。

三、双向带头循环链表的实现

1、创建一个双向带头循环链表节点结构体

可以看到我们上面的结构体中,是有两个指针的,其中一个指针就指向的是下一个节点的,还有一个指针是指向的前一个节点的。 

2、申请节点函数

 

上面这两个操作我们应该都轻车熟路了。

下面我们继续完成双向带头链表的一些操作。

3、创建头节点

 

我们在开始创建头节点,此时整个链表就其一个节点,所以其前一个节点是他,其后一个指针也是他,所以我们要对其两个指针进行初始化。

4、创建一个打印双向带头循环链表的函数

 我们前面学单链表的时候,我们是通过其后续指针进行遍历打印的,那么我们的双向带头指针是否可以呢,我们知道,我们实际上用来存储数据的是不包括头节点的,那么我们要从头节点的下一共节点开始遍历,然后就是遍历结束的条件,我们会发现我们的循环链表,其遍历完一遍后,其指针会再次走到头节点,所以我们的循环条件可以设置成遍历的指针不等于头节点。

代码如下:

5、双向带头循环链表的尾插 

我们如何进行尾插呢,我们的尾插是从链表的最后一个节点进行插入,那么我们的头节点的前一个节点刚刚好就是我们的尾节点,那么我们不需要进行遍历寻找尾节点的地址,可以直接通过头节点的prev指针找到尾节点。然后我们再来分析如何插入,首先我们这个要插入的节点,其指向下一个节点的指针要指向头节点,然后其指向前一个节点的指针要指向当前尾节点指向的前一个指针的节点,然后就是当前尾节点指向后一个节点的指针要指向这个插入的节点,然后头节点指向前一个节点的指针要指向这个要尾插的节点。

下面我们通过代码来实现:

我们可以看到双向带头循环链表的尾插的时间复杂度为O(1),然后空间复杂度也是O(1)。 

 我们测试一下看看:

6、双向带头循环链表的头插 

我们上面实现了尾插,那我们趁热打铁继续实现头插,不过我们要注意的是,我们此时的头插和前面单链表的头插是不一样的,这里的头插是指在头节点的后一个节点进行数据的插入,也就是在整个链表的第二个节点进行插入,那么我们该如何操作呢?

首先我们很快可以想到的是,要插入的这个节点的指向前一个节点的指针要指向头节点,然后其指向下一个节点的指针要指向头节点原来指向的下一个节点,然后原来这个在头节点后面的节点的指向前一个节点指针要指向这个插入的节点。

下面我们通过代码实现一下:

可以看到我们的头插的时间复杂度还是O(1),空间复杂度为O(1)。

下面我们测试一下:

 

7、双向带头循环链表的尾删 

前面我们实现了对于双向带头循环链表的数据的插入,那么我们接下来就实现删除的功能。

我们的尾插要如何实现呢,首先我们肯定还是要找到我们的 尾节点的,这个我们的循环链表也有优势,直接通过头节点可以找到,然后我们可以先创建一个节点指针将其保存起来,然后我们将其前一个节点指向下一个节点的指针指向头节点,然后头节点指向前一个节点的指针指向要删除的这个节点的前一个节点,然后我们再将这个尾节点的空间进行释放,那么我们的尾删也就完成了。

但是我们要注意的是,我们要保证我们尾删,其要有节点被我们删除才行,那么我们要先进行判断,那么我们可以写一个函数,判断其是否有能被我们进行尾删的节点。

代码如下:

我们看看尾删的时间复杂度,我们可以很容易看到尾删的时间复杂度为O(1),空间复杂度为O(1)。

下面我们测试一下是否实现了尾删的功能:

 上面是测试尾删是否成功,下面我们测试当链表只有头节点的情况下,程序是否会终止:

 可以看到我们的尾删也成功实现了。

8、双向带头循环链表的头删

下面我们接着实现其头删的功能,这里和前面的头插一样,我们要删除的是头节点的下一个节点,不是要删除链表的第一个节点,然后我们的删除,要保证除开头节点外,还有其他的有效节点。

那么我们的头删该如何进行呢?

其实也是很简单的,我们可以先创建一个指针将这个要删除的节点存储起来,然后我们让头节点指向下一个节点的指针指向这个要删除的节点的下一个节点,然后这个要删除的节点的下一个节点的指向前一个节点的指针指向头节点,然后我们再将这个节点删除即可。

代码如下:

可以看到我们的头删的时间复杂度也是O(1),然后空间复杂度也是O(1)。

下面我们测试一下:

 

 可以看到我们的头删功能也实现了。

9、双向带头循环链表的指定位置之后插入数据

前面我们实现了尾插和头插功能,那么我们现在来看看在指定位置之后插入数据。

要指向在指定位置之后插入数据,那么我们要找到对应的节点,那么我们可以写一个函数来帮助我们寻找链表中对应的节点。

然后我们拿到这个指定的位置后,那么我们就可以开始插入数据了,那么我们该如何进行插入呢,其实也很简单,我们就让这个要插入的节点,其指向前一个节点的指针指向这个指定的位置,然后指向下一个节点的指针指向这个指定的位置的下一个节点,然后这个指定的位置的原来的下一个节点指向前一个节点的指针指向这个要插入的节点,然后我们的指定位置的指向下一个节点的指针指向这个要插入的节点。而且我们一定要保证这样的顺序才不会导致节点的丢失。

代码如下:

 

我们可以计算到,指定位置之后插入数据的时间复杂度为O(1),空间复杂度为O(1)。

下面我们测试一下代码:

 

10、双向带头循环链表删除指定位置的数据

 首先我们和前面的头删和尾删一样,我们要保证我们的链表有节点被我们进行删除,然后我们就要得到要删除的位置,然后我们就可以进行删除了,那么我们要如何删除指定位置的数据呢?

这个功能还是很简单就可以实现的,我们首先将这个节点的前一个节点指向下一个节点的指针指向指定位置的下一个节点,然后让其后一个节点指向前一个节点的指针指向指定位置的前一个节即可,然后我们再进行删除即可,我们可以发现,我们双向带头循环链表的删除的逻辑大致是一样的。

代码如下:

我们可以看到这个功能的时间复杂度还是O(1),空间复杂度为O(1)。

下面我们测试代码看看:

 

11、销毁链表

我们销毁链表其实大致上和单链表是一样的,就是遍历然后进行删除,要注意的是,我们的头节点也要进行删除,那么我们此时就要对指针进行修改了,那么我们就不能再传一级指针了,如果是一级指针,那么我们再对头节点进行修改是没办法对实参产生影响的,所以我们传参的时候要传二级指针。

如下:

但是其实我们发现我们前面实现的所有功能中,都只是传递的一级指针,那么就这个特殊,那么我们为了街口的统一,我们也可以传一级指针,但是就是对于头节点我们要特殊处理一下,在函数运行结束后,我们手动进行释放头节点。

如下:

 

四、顺序表和链表的分析

 

谢谢大家的观看,如果对小弟的内容认可的话就给个小心心吧。 

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

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

相关文章

mybatis中${}和#{}的区别

先测试&#xff0c;再说结论 userService.selectStudentByClssIds(10000, "wzh or 11");List<StudentEntity> selectStudentByClssIds(Param("stuId") int stuId, Param("field") String field);<select id"selectStudentByClssI…

抗量子计算攻击的数据安全体系构建:从理论突破到工程实践

在“端 - 边 - 云”三级智能协同理论中&#xff0c;端 - 边、边 - 云之间要进行数据传输&#xff0c;网络的安全尤为重要&#xff0c;为了实现系统总体的安全可控&#xff0c;将构建安全网络。 可先了解我的前文&#xff1a;“端 - 边 - 云”三级智能协同平台的理论建构与技术实…

uniapp|实现手机通讯录、首字母快捷导航功能、多端兼容(H5、微信小程序、APP)

基于uniapp实现带首字母快捷导航的通讯录功能,通过拼音转换库实现汉字姓名首字母提取与分类,结合uniapp的scroll-view组件与pageScrollTo API完成滚动定位交互,并引入uni-indexed-list插件优化索引栏性能。 目录 核心功能实现动态索引栏生成​联系人列表渲染​滚动定位联动性…

【Linux】基础IO(二)

&#x1f4dd;前言&#xff1a; 上篇文章我们对Linux的基础IO有了一定的了解&#xff0c;这篇文章我们来讲讲IO更底层的东西&#xff1a; 重定向及其原理感受file_operation文件缓冲区 &#x1f3ac;个人简介&#xff1a;努力学习ing &#x1f4cb;个人专栏&#xff1a;Linux…

【生存技能】ubuntu 24.04 如何pip install

目录 原因解决方案说明关于忽略系统路径 在接手一个新项目需要安装python库时弹出了以下提示: 原因 这个报错是因为在ubuntu中尝试直接使用 pip 安装 Python 包到系统环境中&#xff0c;ubuntu 系统 出于稳定性考虑禁止了这种操作 这里的kali是因为这台机器的用户起名叫kali…

SHAP分析!Transformer-GRU组合模型SHAP分析,模型可解释不在发愁!

SHAP分析&#xff01;Transformer-GRU组合模型SHAP分析&#xff0c;模型可解释不在发愁&#xff01; 目录 SHAP分析&#xff01;Transformer-GRU组合模型SHAP分析&#xff0c;模型可解释不在发愁&#xff01;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 基于SHAP分析…

知名人工智能AI培训公开课内训课程培训师培训老师专家咨询顾问唐兴通AI在金融零售制造业医药服务业创新实践应用

AI赋能未来工作&#xff1a;引爆效率与价值创造的实战营 AI驱动的工作革命&#xff1a;从效率提升到价值共创 培训时长&#xff1a; 本课程不仅是AI工具的操作指南&#xff0c;更是面向未来的工作方式升级罗盘。旨在帮助学员系统掌握AI&#xff08;特别是生成式AI/大语言模型…

Qt Creator 配置 Android 编译环境

Qt Creator 配置 Android 编译环境 环境配置流程下载JDK修改Qt Creator默认android配置文件修改sdk_definitions.json配置修改的内容 Qt Creator配置 异常处理删除提示占用编译报错连接安卓机调试APP闪退 环境 Qt Creator 版本 qtcreator-16.0.1Win10 嗯, Qt这个开发环境有点难…

智能手表蓝牙 GATT 通讯协议文档

以下是一份适用于智能手表的 蓝牙 GATT 通讯协议文档&#xff0c;适用于 BLE 5.0 及以上标准&#xff0c;兼容 iOS / Android 平台&#xff1a; 智能手表蓝牙 GATT 通讯协议文档 文档版本&#xff1a;V1.0 编写日期&#xff1a;2025年xx月xx日 产品型号&#xff1a;Aurora Wat…

RT-THREAD RTC组件中Alarm功能驱动完善

使用Rt-Thread的目的为了更快的搭载工程&#xff0c;使用Rt-Thread丰富的组件和第三方包资源&#xff0c;解耦硬件&#xff0c;在更换芯片时可以移植应用层代码。你是要RTT的目的什么呢&#xff1f; 文章项目背景 以STM32L475RCT6为例 RTC使用的为LSE外部低速32 .756k Hz 的…

用ffmpeg压缩视频参数建议

注意:代码中的斜杠\可以删除 一、基础压缩命令&#xff08;画质优先) libx265​​推荐配置 ffmpeg -i input.mp4 -c:v libx265 -crf 25 -preset medium -c:a aac -b:a 128k output.mp4-crf&#xff1a;建议25-28&#xff08;值越小画质越高&#xff09; -preset&#xff1a;平…

输入顶点坐标输出立方体长宽高的神经网络 Snipaste贴图软件安装

写一个神经网络&#xff0c;我输入立方体投影线段的三视图坐标&#xff0c;输出分类和长宽高 放这了明天接着搞 -------------------------------------------- 开搞 然而我的数据是这样的 winget install Snipaste f1启动&#xff0c;双击贴图隐藏 用右边4个数据做输入…

用python清除PDF文件中的水印(Adobe Acrobat 无法删除)

学校老师发的资料&#xff0c;有时候会带水印&#xff0c;有点强迫症的都想给它去掉。用Adobe Acrobat试了下&#xff0c;检测不到水印&#xff0c;无法删除&#xff01;分析发现原来这类PDF文件是用word编辑的&#xff0c;其中的水印是加在了页眉中&#xff01; 自己动手想办法…

Dagster Pipes系列-1:调用外部Python脚本

本文是"Dagster Pipes教程"的第一部分&#xff0c;介绍如何通过Dagster资产调用外部Python脚本并集成到数据管道中。首先&#xff0c;创建Dagster资产subprocess_asset&#xff0c;利用PipesSubprocessClient资源执行外部脚本external_code.py&#xff0c;实现跨进程…

python shutil 指定文件夹打包文件为 zip 压缩包

python shutil 指定文件夹打包文件为 zip 压缩包&#xff0c;具体代码如下&#xff1a; import shutil# 指定要打包的文件夹路径 src_doc ./test# 指定输出的压缩包文件名&#xff08;不包含扩展名&#xff09; output_filename testfromat_ zip# 打包并压缩文件夹为 ZIP …

Webug4.0通关笔记25- 第30关SSRF

目录 一、SSRF简介 1.SSRF原理 2.渗透方法 二、第30关SSRF渗透实战 1.打开靶场 2.渗透实战 &#xff08;1&#xff09;Windows靶场修复 &#xff08;2&#xff09;Docker靶场修复 &#xff08;3&#xff09;获取敏感文件信息 &#xff08;4&#xff09;内网端口与服务…

OpenCV 中用于背景分割的一个类cv::bgsegm::BackgroundSubtractorLSBP

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::bgsegm::BackgroundSubtractorLSBP 是 OpenCV 中用于背景分割的一个类&#xff0c;它基于局部样本二进制模式&#xff08;Local Sample Bina…

MacOS 上构建 gem5

MacOS 中只存在 python3&#xff0c;但是scons 只认 python&#xff0c;不在 系统中创建 软连接&#xff0c;一个是因为比较难操作&#xff1b;另一个是尽量不要更改系统。所以独立构件python 和scons&#xff1a; 1&#xff0c;安装python 下载源代码&#xff1a; Python S…

认识中间件-以及两个简单的示例

认识中间件-以及两个简单的示例 什么是中间件一个响应处理中间件老朋友 nest g如何使用为某个module引入全局引入编写逻辑一个日志中间件nest g mi 生成引入思考代码进度什么是中间件 官方文档 中间件是在路由处理程序之前调用的函数。中间件函数可以访问请求和响应对象,以及…

(五)毛子整洁架构(分布式日志/Redis缓存/OutBox Pattern)

文章目录 项目地址一、结构化日志1.1 使用Serilog1. 安装所需要的包2. 注册服务和配置3. 安装Seq服务 1.2 添加分布式id中间件1. 添加中间件2. 注册服务3. 修改Application的LoggingBehavior 二、Redis缓存2.1 添加缓存1. 创建接口ICaching接口2. 实现ICaching接口3. 注册Cachi…