【数据结构】双向循环链表专题解析

news2024/11/29 22:44:18

实现自己既定的目标,必须能耐得住寂寞单干。💓💓💓

目录

•✨说在前面

🍋知识点一:双向链表的结构

 • 🌰1."哨兵位"节点

 • 🌰2.双向带头循环链表的结构

🍋知识点二:双向带头循环链表

 • 🌰1. 动态申请节点 

 • 🌰2. 双向链表的初始化

 • 🌰3. 双向链表元素的打印

 • 🌰4. 双向链表头部插入数据

 • 🌰5. 双向链表尾部插入数据

 • 🌰6. 指定位置pos之后插入数据

 • 🌰7.双向链表头部删除元素

 • 🌰8.双向链表尾部删除元素

 • 🌰9.删除指定位置pos节点

 • 🌰10.双向链表的查找

 • 🌰10.双向链表的销毁

• ✨SumUp结语


•✨说在前面

亲爱的读者们大家好!💖💖💖,我们又见面了,之前我们学习了顺序表后,又紧接着给大家讲解了链表中最典型的单向不循环链表,也是最常用的一种。但正所谓我们学习应该面面俱到,有了之前的学习基础,再学习双向链表实际上是非常简单的。

  

 如果你没有准备好的话,可以再复习一下单链表以及单链表相关LeetCode的OJ题。

   

👇👇👇
💘💘💘知识连线时刻(直接点击即可)

  🎉🎉🎉复习回顾🎉🎉🎉

【数据结构】单链表专题详细分析

    

  博主主页传送门:愿天垂怜的博客

 

 

🍋知识点一:双向链表的结构

 • 🌰1."哨兵位"节点

哨兵位指的是链表中指向链表第一个节点的节点,哨兵位不存储任何有效元素,只是在那里放哨的,顾称为哨兵位节点

注意:

这里的"带头"跟前面我们说的"链表中的第一个有效节点"是两个概念,实际单链表的头结点不是第一个有效节点,而是哨兵位节点。

"哨兵位"存在的意义:

遍历循环链表避免死循环。

具体带头比不带头有什么优势可以看我上一篇文章中的合并有序链表。

LeetCode/NowCoder-链表经典算法OJ练习1

 • 🌰2.双向带头循环链表的结构

结构如下:

 由于"哨兵位"节点的存在,我们再实现这样的链表时可以省去一些内容:

🎉插入操作时,不需要检查是否在头部插入,因为哨兵节点作为头结点,总是存在。

🎉删除操作时,不需要处理删除的是否是头节点的情况,因为哨兵节点不会被删除。

🎉简化了代码,因为不需要为头节点和普通节点编写不同的处理逻辑。

类比单链表的结构,可以定义出节点数据为整型的双向带头循环链表节点:

typedef int LTDataType;

typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;//指向前一个节点
	struct ListNode* next;//指向后一个节点
}LTNode;

🍋知识点二:双向带头循环链表

 • 🌰1. 动态申请节点 

在双向带头循环链表提供的方法中,动态申请节点是必不可少的。

LTNode* LTBuyNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = x;
	newnode->next = newnode->prev = newnode;
}

初始化双向链表其实就是创建头节点,那么就需要用到LTBuyNode来申请节点。由于是双向的,我们可以让它的next指针和prev指针先指向它自己。

那是否可以将哨兵位的next指针和prev指针初始化为NULL呢?答案是不行的。

如果哨兵位的前驱指针prev和后继指针next初始化为NULL,这样的链表虽然满足了双向,也满足了带头,但是不满足循环,所以不能这样初始化,因此在动态申请节点的时候,就让newnode的prev和next都指向它自己,这样就可以循环起来了。

 • 🌰2. 双向链表的初始化

初始化双向带头循环链表实际上就是创建"哨兵位"头节点。

写法1:传二级指针,函数为void型。

void LTInit(LTNode** pphead)
{
	*pphead = LTBuyNode(-1);
}

写法2:不传参 ,在函数中创建节点,函数为LTNode*型。

LTNode* LTInit()
{
    LTNode* phead = LTBuyNode(-1);
	return phead;
}

我们将其中存储的值设为-1,表示其为头节点,此时链表中只有一个头节点:

 • 🌰3. 双向链表元素的打印

打印双向链表中的所有节点数据。

void LTPrint(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

这个没什么好说的,和之前我们学习的单链表基本是一样的,不过是while循环的条件有所变化。我们如果初始化pcur为phead->next,那它循环完一轮之后会重新变成phead,此时就已经打印完成,不需要继续循环了。

 • 🌰4. 双向链表头部插入数据

向双向链表的头部插入节点和数据,指的是在头节点的后一个位置插入数据,而不是在头节点的前面(头插)。

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = phead;
	newnode->next = phead->next;
	phead->next = newnode;
	newnode->next->prev = newnode;
}

双向带头循环链表的指针关系比较复杂,但是逻辑却很简单,我们一定要通过画图来理解,不要光看代码,这样是看不出东西的。

上述关系红色为先修改,蓝色为后修改。由于关系比较多,可以先从newnode入手,先处理newnode的prev和next指针,这样不会影响到原链表的结构。当设置完newnode时,如果先让phead的后继指针next指向newnode,那么图中红色所标注的phead->next不在是那个节点的地址,而是newnode的地址,此时它就应该是newnode->next。

 • 🌰5. 双向链表尾部插入数据

向双向链表的尾部插入节点和数据,可以理解为在图中的末尾插入数据,也可以理解为在头节点的前一个位置插入数据。

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = phead->prev;
	newnode->next = phead;
	phead->prev->next = newnode;
	phead->prev = newnode;
}

由于是循环链表,画图的时候可以在节点顺序不改变的前提下灵活变换图像。

图1:

图2:


 两中图理解都是可以的,由于头节点的位置固定,上面两个图都是同一个双向链表。 

 • 🌰6. 指定位置pos之后插入数据

在指定位置pos的后面插入节点和数据,其实和头插方法很类似,甚至可以说头插其实就是pos后插入数据的一种特殊情况,只不过此时pos=phead而已。

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = pos;
	newnode->next = pos->next;
	pos->next = newnode;
	newnode->next->prev = newnode;
}

可以发现,代码也是及其类似。

 • 🌰7.双向链表头部删除元素

删除链表中的第一个有效节点,即删除头节点的后一个节点。

void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->next;
	phead->next = del->next;
	del->next->prev = phead;
	free(del);
	del = NULL;
}

为了防止free释放后无法解引用找到下一个节点的问题,引入del保存要删除的节点的地址。

 • 🌰8.双向链表尾部删除元素

 删除双向链表中的最后一个数据,可以理解为删除图中末尾的数据,也可以理解为删除头节点前面一个数据。

void LTPopBack(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->prev;
	phead->next = del->prev;
	del->prev->next = phead;
	free(del);
	del = NULL;
}

两种画法和2.5思路一样,这里就画出一种,只要理解一种,另一种就很简单了。

 • 🌰9.删除指定位置pos节点

删除指定位置pos的节点,如法炮制。

void LTErase(LTNode* pos)
{
	//pos不能为哨兵位
	assert(pos);
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	pos = NULL;
}

除了要保证pos不为NULL外,还需要保证pos不能为哨兵位,否则它不是有效节点。

 • 🌰10.双向链表的查找

查找链表中值为x的节点。

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

定义pcur,利用while循环遍历一轮,如果有值为x的节点,则返回该节点,遍历一轮后若仍没有该节点,则返回空指针NULL。

 • 🌰10.双向链表的销毁

与单链表和顺序表的思想如出一辙,如法炮制。

void LTDestory(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

• ✨SumUp结语

数据结构的学习一定要多画图,多理解,多思考,切忌直接抄写代码,就认为自己已经会了,一定到自己动手,才能明白自己哪个地方有问题。

如果大家觉得有帮助,麻烦大家点点赞,如果有错误的地方也欢迎大家指出~

 

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

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

相关文章

Java - Json字符串转List<LinkedHashMap<String,String>>

需求&#xff1a;在处理数据时&#xff0c;需要将一个Object类型对象集合转为有序的Map类型集合。 一、问题 1.原代码&#xff1a; 但在使用时出现报错&#xff1a; Incompatible equality constraint: LinkedHashMap<String, String> and LinkedHashMap 不兼容的相等…

社区送水小程序软件开发

uni-app框架&#xff1a;使用Vue.js开发跨平台应用的前端框架&#xff0c;编写一套代码&#xff0c;可编译到Android、小程序等平台。 框架支持:springboot/Ssm/thinkphp/django/flask/express均支持 前端开发:vue.js 可选语言&#xff1a;pythonjavanode.jsphp均支持 运行软件…

猫头虎分享已解决Bug || 已解决ERROR: Ruby Gems安装中断 ⚠️ Bug 报告:Gem::RemoteFetcher::FetchError

猫头虎分享已解决Bug || 已解决ERROR: Ruby Gems安装中断 ⚠️ Bug 报告&#xff1a;Gem::RemoteFetcher::FetchError 博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; …

四川汇昌联信:拼多多运营属于什么行业?

拼多多运营属于什么行业?这个问题看似简单&#xff0c;实则涉及到了电商行业的深层次理解。拼多多运营&#xff0c;顾名思义&#xff0c;就是在拼多多这个电商平台上进行商品销售、推广、客户服务等一系列活动。那么&#xff0c;这个行业具体包含哪些内容呢?下面就从四个不同…

redis深入理解之实战

1、SpringBoot整合redis 1.1 导入相关依赖 <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId> </dependency> <dependency><groupId>org.springframework.boot</groupId><artifactId&g…

Webstorm免费安装教程

一、介绍 WebStorm 具有智能化的代码编辑功能&#xff0c;如代码补全、重构、代码导航、错误检测等等&#xff0c;这些功能可以帮助开发人员提高编码效率&#xff0c;减少出错的可能性。同时&#xff0c;WebStorm 也集成了各种流行的前端框架和库&#xff0c;如 React、Angula…

Python3实现三菱PLC串口通讯(附源码和运行图)

基于PyQt5通过串口通信控制三菱PLC 废话不多说&#xff0c;直接上源码 """ # -*- coding:utf-8 -*- Project : Mitsubishi File : Main_Run.pyw Author : Administrator Time : 2024/05/09 下午 04:10 Description : PyQt5界面主逻辑 Software:PyCharm "…

【Linux】轻量级应用服务器如何开放端口 -- 详解

一、测试端口是否开放 1、测试程序 TCP demo 程序&#xff08;可参考&#xff1a;【Linux 网络】网络编程套接字 -- 详解-CSDN博客&#xff09; 2、测试工具 Windows - cmd 窗口 输入命令&#xff1a;telnet [云服务器的公网ip] [port] 二、腾讯云安全组开放端口 1、安全组设…

简单贪吃蛇的实现

贪吃蛇的实现是再windows控制台上实现的&#xff0c;需要win32 API的知识 Win32 API-CSDN博客https://blog.csdn.net/bkmoo/article/details/138698452?spm1001.2014.3001.5501 游戏说明 ●地图的构建 ●蛇身的移动&#xff08;使用↑ . ↓ . ← . → 分别控制蛇的移动&am…

【C++】list原理讲解及其实现

目录 一、认识list底层结构 二、list的构造类函数 三、迭代器 四、数据的访问 五、容量相关的函数 六、关于数据的增删查改操作 前言 要模拟实现list&#xff0c;必须要熟悉list的底层结构以及其接口的含义&#xff0c;在上一篇我们仔细讲解了list的常见接口的使用及其含义&…

consul启动Error_server_rejoin_age_max (168h0m0s) - consider wiping your data dir

consul 启动报错&#xff1a; consul[11880]: 2024-05-12T08:37:51.095-0400 [ERROR] agent: startup error: error"refusing to rejoin cluster because server has been offline for more than the configured server_rejoin_age_max (168h0m0s) - consider wiping you…

IntelliJ的Maven编译返回找不到有效证书

文章目录 小结问题及解决找不到有效证书找不到org.springframework.stereotype.Service问题IntelliJ: Cannot resolve symbol springframework 参考 小结 将IntelliJ工程拷贝到新的机器中&#xff0c;返回Maven编译返回找不到有效证书的问题&#xff0c;进行了解决。 问题及解…

通过内网穿透实现远程访问个人电脑资源详细过程(免费)(NatApp + Tomcat)

目录 1. 什么是内网穿透 2. 内网穿透软件 3. NatApp配置 4. 启动NatApp 5. 通过内网穿透免费部署我们的springboot项目 通过内网穿透可以实现远程通过网络访问电脑的资源&#xff0c;本文主要讲述通过内网穿透实现远程访问个人电脑静态资源的访问&#xff0c;下一章节将讲…

Java入门基础学习笔记18——赋值运算符

赋值运算符&#xff1a; 就是“”&#xff0c;就是给变量赋值的&#xff0c;从右边往左边看。 int a 10; // 把数据赋值给左边的变量a存储。 扩展赋值运算符&#xff1a; 注意&#xff1a;扩展的赋值运算符隐含了强制类型转换。 package cn.ensource.operator;public class…

Unity Animation--动画窗口指南(使用动画视图)

Unity Animation--动画窗口指南&#xff08;使用动画视图&#xff09; 使用动画视图 window -> Animation 即可打开窗口 查看GameObject上的动画 window -> Animation -> Animation 默认快捷键 Ctrl 6 动画属性列表 在下面的图像中&#xff0c;“动画”视图&am…

【LAMMPS学习】八、基础知识(6.3)使用 LAMMPS GUI

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语,以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各种模拟。 …

合并连个有序链表(递归)

21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; 2.讲解算法原理 2.1重复子问题 2.2只关心其中的一个子问题是如何解决的 2.3细节&#xff0c;递归出口 3.小总结 &#xff08;循环&#xff08;迭代&#xff09;VS 递归&#xff09;&#xff08;递归VS深搜&…

49. UE5 RPG 使用Execution Calculations处理对目标造成的最终伤害

Execution Calculations是Unreal Engine中Gameplay Effects系统的一部分&#xff0c;用于在Gameplay Effect执行期间进行自定义的计算和逻辑操作。它允许开发者根据特定的游戏需求&#xff0c;灵活地处理和修改游戏中的属性&#xff08;Attributes&#xff09;。 功能强大且灵…

AI 重塑产品设计

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

系统设计中的泛化调用

背景 目前在学习一些中间件&#xff0c;里面看到了一个词是叫泛化调用&#xff0c; 其实这个场景在JAVA中比较常见。我们常用的有反射&#xff0c;反射就是我知道类名称、类方法和参数&#xff0c;调用一个Object的类&#xff0c;但是在HTTP或者RPC远程调用过程中&#xff0c;…