数据结构——lesson4带头双向循环链表实现

news2025/2/24 11:05:34

前言✨✨

💥个人主页:大耳朵土土垚-CSDN博客

💥 所属专栏:数据结构学习笔记​​​​​​

💥双链表与单链表的区分:单链表介绍与实现

💥对于malloc函数有疑问的:动态内存函数介绍

   感谢大家的观看与支持🌹🌹🌹 

   有问题可以写在评论区或者私信我哦~

 

目录

前言✨✨

一、💥💥什么是带头双向循环链表?

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

1 .搭建链表基础

2.从内存中开辟一个节点

3. 创建返回链表的头结点

4.双向链表销毁

5.双向链表打印 

6.双向链表尾插 

7.双向链表尾删

8.双向链表头插 

9.双向链表头删 

10.双向链表查找

11.双向链表在pos的前面进行插入 

12.双向链表删除pos位置的节点 

三、💫💫拓展

四、🎉🎉结言 


一、💥💥什么是带头双向循环链表?

 

带头双向循环链表(Doubly Circular Linked List with a Head)是一种链表数据结构,它具有以下特点:

1.头节点:带头双向循环链表包含一个头节点,它位于链表的起始位置,并且不存储实际数据。头节点的前驱指针指向尾节点,头节点的后继指针指向第一个实际数据节点。

2.循环连接:尾节点的后继指针指向头节点,而头节点的前驱指针指向尾节点,将链表形成一个循环连接的闭环。这样可以使链表在遍历时可以无限循环,方便实现循环操作。

3.双向连接:每个节点都有一个前驱指针和一个后继指针,使得节点可以向前和向后遍历。前驱指针指向前一个节点,后继指针指向后一个节点。

        总结:带头双向循环链表可以支持在链表的任意位置进行插入和删除操作,并且可以实现正向和反向的循环遍历。通过循环连接的特性,链表可以在连续的循环中遍历所有节点,使得链表的操作更加灵活和高效。

如下图所示:

 

 

结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。 

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

1 .搭建链表基础

带头双向循环链表需要三个变量,两个存放指向前后节点的指针,另一个存放数据

// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;//存放数据
	struct ListNode* next;//指向下一个节点
	struct ListNode* prev;//指向上一个节点
}ListNode;

2.从内存中开辟一个节点

使用malloc函数开辟节点

//从内存中开辟一个节点
ListNode* BuyNode(LTDataType x)
{
	ListNode* buynode = (ListNode*)malloc(sizeof(struct ListNode));
	if (buynode == NULL)//开辟失败
	{
		perror("malloc fail");
	}
	buynode->data = x;
	buynode->next = NULL;
	buynode->prev = NULL;
	
}

 

3. 创建返回链表的头结点
 

开始时头节点两个指针都指向自己

//创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* head = BuyNode(-1);//这里将头节点数据设为-1,任意数都可以
	head->next = head;
	head->prev = head;
	return head;
}

 

4.双向链表销毁

 malloc开辟空间后要使用free销毁内存空间,防止内存泄漏

// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;//头节点最后销毁
	while (cur != pHead)//循环一遍
	{
		ListNode* next = cur->next;//保存下一个节点,防止丢失
		free(cur);//销毁节点
		cur = next;
	}
	free(pHead);//销毁头节点
}

5.双向链表打印 

 

//双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	if (pHead->next == pHead)//没有节点的情况,也可以不考虑
	{
		printf("pHead<=>pHead");
		return;
	}
	//有节点的情况
	printf("pHead<=>");//先打印pHead
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("pHead");//因为最后也是指向pHead
}

 

没有节点情况打印如下: 

6.双向链表尾插 

 

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	
	//找尾节点,保存原来的尾
	//尾节点就是pHead->prev
	ListNode* tail = pHead->prev;
	//开辟新节点
	ListNode* newnode = BuyNode(x);
	//尾插
	pHead->prev = newnode;
	newnode->next = pHead;
	newnode->prev = tail;
	tail->next = newnode;

}

 

结果如下:

 

7.双向链表尾删

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	//没有节点不能尾删,头节点pHead不算
	if (pHead->next == pHead)
	{
		printf("没有添加节点\n");
		return;
	}
	//找尾节点,以及尾节点的前一个节点
	ListNode* tail = pHead->prev;
	ListNode* tailprev = tail->prev;
	//尾删
	tailprev->next = pHead;
	pHead->prev = tailprev;
	free(tail);//释放内存空间
}

 结果如下:

8.双向链表头插 

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//找头以外的第一个节点
	ListNode* headnext = pHead->next;
	//创建新节点
	ListNode* newnode = BuyNode(x);
	//头插
	pHead->next = newnode;
	newnode->next = headnext;
	newnode->prev = pHead;
	headnext->prev = newnode;
}

 

结果如下:

9.双向链表头删 

 

// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	//判断有没有节点,头节点pHead除外
	if (pHead->next == pHead)
	{
		printf("没有添加节点\n");
		return;
	}
	//有节点
	//找头节点以及头节点的下一个节点
	ListNode* head = pHead->next;
	ListNode* headnext = head->next;
	//头删
	pHead->next = headnext;
	headnext->prev = pHead;
	free(head);//释放内存空间
}

 

 结果如下:

 

10.双向链表查找

// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//判断有无节点
	if (pHead->next == pHead)
	{
		printf("没有添加节点\n");
		return;
	}
	ListNode* cur = pHead->next;
	//遍历查找
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;//找到返回地址
		}
		cur = cur->next;
	}
}

结果如下:

11.双向链表在pos的前面进行插入 

在pos位置前面插入原理和头插尾插相似

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	//找到pos前一个节点
	ListNode* posprev = pos->prev;
	//创建新节点
	ListNode* newnode = BuyNode(x);
	//在pos前插入
	posprev->next = newnode;
	newnode->next = pos;
	newnode->prev = posprev;
	pos->prev = newnode;
	
}

结果如下:

 

12.双向链表删除pos位置的节点 

在pos位置删除原理和头删尾删相似

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);
	//找到pos前一个节点
	ListNode* posprev = pos->prev;
	//找打pos后一个节点
	ListNode* posnext = pos->next;
	//删除pos位置节点
	posprev->next = posnext;
	posnext->prev = posprev;
	free(pos);//释放内存空间

}

结果如下:

三、💫💫拓展

思考:在pos之前插入与头插尾插是否有关?

           在pos位置删除与头删尾删是否相似?

 

我们发现pos位置前插入函数代码似乎可以复用在头插尾插;

pos位置删除函数代码似乎可以复用在头删尾删;

下面我们一起来实现

1.尾插头插 

//尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	
	//找尾节点,保存原来的尾
	//尾节点就是pHead->prev
	//ListNode* tail = pHead->prev;
	开辟新节点
	//ListNode* newnode = BuyNode(x);
	尾插
	//pHead->prev = newnode;
	//newnode->next = pHead;
	//newnode->prev = tail;
	//tail->next = newnode;

	ListInsert(pHead, x);

}

//头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//找头以外的第一个节点
	//ListNode* headnext = pHead->next;
	创建新节点
	//ListNode* newnode = BuyNode(x);
	头插
	//pHead->next = newnode;
	//newnode->next = headnext;
	//newnode->prev = pHead;
	//headnext->prev = newnode;

	ListInsert(pHead->next, x);

}

2.尾删,头删

 

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	//没有节点不能尾删,头节点pHead不算
	if (pHead->next == pHead)
	{
		printf("没有添加节点\n");
		return;
	}
	找尾节点,以及尾节点的前一个节点
	//ListNode* tail = pHead->prev;
	//ListNode* tailprev = tail->prev;
	尾删
	//tailprev->next = pHead;
	//pHead->prev = tailprev;
	//free(tail);//释放内存空间

	ListErase(pHead->prev);
}




// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	//判断有没有节点,头节点pHead除外
	if (pHead->next == pHead)
	{
		printf("没有添加节点\n");
		return;
	}
	有节点
	找头节点以及头节点的下一个节点
	//ListNode* head = pHead->next;
	//ListNode* headnext = head->next;
	头删
	//pHead->next = headnext;
	//headnext->prev = pHead;
	//free(head);//释放内存空间

	ListErase(pHead->next);
}

 

 运行结果依然不受影响:

 

四、🎉🎉结言 

        我们通过上面的学习发现,相似的代码的重复利用可以大大减少我们写代码的时间与精力,提高我们工作学习的效率;双向链表尽管结构较单链表复杂,但其实现却比单链表简单得多,相信大家对此都深有体会,此外数据结构的题目我们可以通过画图来很好的获得思路与接替步骤,以上就是带头双向循环链表的相关知识啦~完结撒花~🎉🎉🌹🌹🌹

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

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

相关文章

J1—Vivado调试技巧VIO IP

1.简介 VIO&#xff08;Virtual Input/Output&#xff09;IP核是一种用于FPGA设计的IP核&#xff0c;它可以模拟输入/输出设备的功能&#xff0c;如键盘、鼠标、显示器等。VIO IP核可以在FPGA设计中用于调试和验证&#xff0c;帮助工程师快速定位问题并进行调试。如图所示&…

vue 解决:点击左侧相同菜单,右侧页面不重新加载的问题

1、问题描述&#xff1a; 其一、需求为&#xff1a; 无论是通过路由组件形成的平台管理系统&#xff0c;还是通过文件配置形成的平台管理系统&#xff0c;都存在通过切换左侧的导航栏而使右侧的页面切换的业务需求&#xff1b; 其二、问题描述为&#xff1a; A、步骤一&#…

全国产飞腾E2000Q +复旦微FPGA的轨道交通、电力解决方案

产品概述 ITX-XMF201是一款高性能边缘计算网关主板&#xff0c;采用飞腾E2000Q 4核处理器&#xff0c;国产化率达到95%国产化。 板载2电口&#xff0c;2路CAN&#xff0c;6路RS232接口&#xff0c;1路RS485接口&#xff0c;16路GPIO&#xff0c;可以满足银行、轨道交通、电力等…

springboot2入门到实战 - JWT

JWT是什么&#xff1f; JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object。 This information can be verified and trusted because it is digi…

便签软件哪个好用?好用便签怎么设置提醒?

在当今信息爆炸的时代&#xff0c;便签软件成为了人们生活中不可或缺的工具之一。那么&#xff0c;便签软件哪个好用呢&#xff1f;下面为您推荐几款备受好评的便签软件。首先是知名度极高的好用便签&#xff0c;它拥有强大的笔记功能、提醒功能和多端同步&#xff0c;让您随时…

COMPOSER安装使用WIN下升级PHP-V

想用TP6使用phpspreadsheet但是说我PHP版本低&#xff0c;原来是PHP7.0 composer要求至少7.4 直接修改环境变量&#xff0c;把PHP目录切换到7.4 composer升级比较简单&#xff0c;在PHP目录下CMD然后官网的命令执行下即可 下面就可以在TP根目录下执行命令安装PHPSPREADSHEET…

Java进阶-集合(3)与泛型

这次介绍集合中的Iterator迭代器&#xff0c;以及泛型。简单来说&#xff0c;泛型对集合的元素类型进行了限制&#xff0c;使用泛型可以在编译时检查类型安全&#xff0c;提高代码的重用率。内容如下 一、Iterator迭代器 1、概念 Iterator迭代器是一个接口&#xff0c;作用…

MATLAB环境下脑电信号EEG的谱分析

脑电信号一直伴随着人类的生命&#xff0c;脑电波是脑神经细胞发生新陈代谢、离子交换时细胞群兴奋突触电位总和&#xff0c;脑电信号的节律性则和丘脑相关&#xff0c;含有丰富的大脑活动信息。通常我们所接触的脑电图都是头皮脑电图&#xff0c;在有些特殊场合还需要皮下部位…

TikTok网络相关问题详解来了,附原生住宅代理IP供应商推荐,

想要迈过TikTok新手门槛&#xff0c;首先必须要学习的就是网络问题。很多人开始做TikTok账号或者TikTok小店时&#xff0c;都会遇到一些先前没有遇到的词汇和概念&#xff0c;比如原生IP&#xff0c;独享IP&#xff0c;甚至专线&#xff0c;那么一个IP可以做几个账号呢&#xf…

0粉低成本带货!职人号矩阵正成为商家的香饽饽

近年来&#xff0c;中国消费市场变化不断&#xff0c;线上消费持续上涨&#xff0c;线上线下一体化成为零售行业的发展新趋势。 加上抖音等平台都在大力发展本地生活&#xff0c;众多连锁商家、本地商家、百货商场纷纷加快数字化转型步伐&#xff0c;掘金线上海量流量&#xff…

机器学习:原理、应用与未来展望

第一章 是什么 机器学习&#xff08;Machine Learning&#xff09;是一门跨学科的学科&#xff0c;它使用计算机模拟或实现人类学习行为&#xff0c;通过不断地获取新的知识和技能&#xff0c;重新组织已有的知识结构&#xff0c;从而提高自身的性能。机器学习涉及多个学科&am…

HGAME 2024 WEEK4 WP

文章目录 IOTez7621 MISCezKeyboardMaybezip**Mondrians &#x1f511; REchange webReverse and Escalation. 想念21和22年的平台和week4的 6557225了 IOT ez7621 拿到固件直接binwalk解&#xff0c;之后grep出hgame 在usr/lib/opkg/info/kmod-flag.control找到这个&#x…

Quartz 任务调度框架源码阅读解析

概念: quartz 是一个基于JAVA的定时任务调度框架 案例: <dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz</artifactId><version>2.3.0</version></dependency>JobDetail job JobBuilder.newJob(Sc…

TP6上传图片到OSS(记录贴)

1&#xff0c;先安装&#xff0c;我使用composer安装 在项目的根目录运行composer require aliyuncs/oss-sdk-php 2,安装成功以后vendor目录下可以看到如图&#xff1a; 3&#xff0c;上传图片代码如下&#xff1a; <?php namespace app\controller;use app\BaseControll…

宝塔FTP服务设置并结合cpolar内网穿透实现远程传输文件

文章目录 1. Linux安装Cpolar2. 创建FTP公网地址3. 宝塔FTP服务设置4. FTP服务远程连接小结 5. 固定FTP公网地址6. 固定FTP地址连接 宝塔FTP是宝塔面板中的一项功能&#xff0c;用于设置和管理FTP服务。通过宝塔FTP&#xff0c;用户可以创建FTP账号&#xff0c;配置FTP用户权限…

D*算法超详解 (D星算法 / Dynamic A*算法/ Dstar算法)(死循环解决--跟其他资料不一样奥)

所需先验知识&#xff08;没有先验知识可能会有大碍&#xff0c;了解的话会对D*的理解有帮助&#xff09;&#xff1a;A*算法/ Dijkstra算法 何为D*算法 Dijkstra算法是无启发的寻找图中两节点的最短连接路径的算法&#xff0c;A*算法则是在Dijkstra算法的基础上加入了启发函数…

数据抽取平台pydatax介绍--实现和项目使用

数据抽取平台pydatax实现过程中&#xff0c;有2个关键点&#xff1a; 1、是否能在python3中调用执行datax任务&#xff0c;自己测试了一下可以&#xff0c;代码如下&#xff1a; 这个str1就是配置的shell文件 try:result os.popen(str1).read() except Exception as …

吴恩达机器学习全课程笔记第四篇

目录 前言 P61-P68 激活函数 Softmax算法 P69-P73 Adam算法 更多类型的层 模型评估 P74-P79 偏差和方差 建立表现基准 学习曲线 偏差和方差与神经网络 前言 这是吴恩达机器学习笔记的第四篇&#xff0c;第三篇笔记请见&#xff1a; 吴恩达机器学习全课程笔记第…

ubuntu20.04 ROS-Noetic 配置qtcreator的ROS环境

文章目录 1 安装qtcreator1.1 下载安装Qt1.2 配置命令启动qtcreator2 配置ROS2.1 直接安装qtcreator-ros2.2 在qtcreator上安装ros_qtc_plugin插件3 注意3.1 构建套件3.2 更新、删除qt4 参考链接1 安装qtcreator QT官网:Qt Downloads 下载包链接:qt5.12.12 Qt5.12.12默认qtc…

如何系统性的学习推荐系统?

推荐一本适合推荐系统、计算广告、个性化搜索领域的从业人员阅读的书&#xff1a;《互联网大厂推荐算法实战》。快手公司算法专家10余年的实战经验总结。涵盖一线互联网公司当前采用的主流推荐算法&#xff0c;凸显可用性、实用性提供从算法基本原理&#xff0c;到技术框架再到…