【数据结构】双向带头循环链表的实现

news2024/12/30 2:00:05

目录

全部代码

图例(双向带头循环链表)

各个功能的实现

 创建该链表的节点

创建初始链表 

链表的头插

链表的尾插

链表的随机插入

链表的头删

链表的尾删

链表的随机删除

链表的销毁

链表是否为空的判断

链表节点的创建 

总结


全部代码


typedef int LTDataType;
typedef struct ListNode
{
	LTDataType val;
	ListNode* next;
	ListNode* prev;
}ListNode;
//判断链表是否为空
bool ListEmpty(ListNode* phead)
{
	assert(phead);
	return !(phead->next == phead);
}
//头删
void ListPopfront(ListNode* phead)
{
	assert(phead);
	assert(ListEmpty(phead));
	ListNode* first = phead->next;
	ListNode* second = first->next;


	phead->next = second;
	second->prev = phead;
}
//尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(ListEmpty(phead));
	ListNode* tail = phead->prev;
	ListNode* prevtail = tail->prev;
	free(tail);
	phead->prev = prevtail;
	prevtail->next = phead;
}
//打印链表
void ListPrint(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	printf("哨兵位<<==>>");
	while (cur != phead)
	{
		printf("%d<<==>>", cur->val);
		cur = cur->next;
	}
	printf("\n");
}
//创建链表
ListNode* ListCreate()
{
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	assert(phead);
	phead->val = -1;
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
//创建新的节点
ListNode* BuyNode(int x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	assert(newnode);
	newnode->val = x;
	newnode->prev = NULL;
	newnode->next = NULL;
	return newnode;
}
//尾插
void PushBack(ListNode* phead,LTDataType x)
{
	assert(phead);
	ListNode* newnode = BuyNode(x);
	ListNode* tail = phead->prev;
	newnode->prev = tail;
	tail->next = newnode;
	phead->prev = newnode;
	newnode->next = phead;
}
//头插
void PushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* first = phead->next;
	ListNode* newnode = BuyNode(x);
	newnode->next = first;
	first->prev = newnode;
	newnode->prev = phead;
	phead->next = newnode;
}


//链表的查找
ListNode* ListFind(ListNode* phead, LTDataType pos)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->val == pos)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//在pos前面进行插入
void ListInsert(ListNode* phead,LTDataType pos,LTDataType x)
{
	assert(phead);
	ListNode* des = ListFind(phead,pos);
	ListNode* prev = des->prev;
	ListNode* newnode = BuyNode(x);
	newnode->next = des;
	des->prev = newnode;
	newnode->prev = prev;
	prev->next = newnode;
}
//删除目标节点
void ListErase(ListNode* phead, LTDataType pos)
{
	assert(phead);
	assert(ListEmpty(phead));
	ListNode* tail = ListFind(phead,pos);
	ListNode* prev = tail->prev;
	ListNode* next = tail->next;


	prev->next = next;
	next->prev = prev;
	free(tail);
}
//链表的销毁
void DestroyList(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	printf("链表销毁成功\n");
}


图例(双向带头循环链表)

 顾名思义,这种链表有前指针和后指针,并且带有哨兵位,另外也是循环的,这种结构是非常优秀的,增删查改那是嘎嘎顺手。

各个功能的实现

 创建该链表的节点

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType val;
	ListNode* next;
	ListNode* prev;
}ListNode;

在节点里设置前后两个指针,以达到双向的目的。

创建初始链表 

//创建链表
ListNode* ListCreate()
{
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	assert(phead);
	phead->val = -1;
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

先创建好哨兵位,并让前后指针都指向哨兵位以完成链表的初始化。

链表的头插

void PushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* first = phead->next;
	ListNode* newnode = BuyNode(x);
	newnode->next = first;
	first->prev = newnode;
	newnode->prev = phead;
	phead->next = newnode;
}

 

assert(phead);:使用 assert() 确认输入的链表头指针不为空。
ListNode* first = phead-&gt;next;:将指针 first 指向链表的第一个节点,以备后续将新节点插入到第一个位置。
ListNode* newnode = BuyNode(x);:使用函数 BuyNode() 创建值为 x 的新节点,该函数的实现不在此代码段中。
newnode-&gt;next = first;:将新节点的 next 指针指向原来的第一个节点。
first-&gt;prev = newnode;:将原来的第一个节点的 prev 指针指向新节点。
newnode-&gt;prev = phead;:将新节点的 prev 指针指向链表头节点,即使它成为了链表的第一个节点。
phead-&gt;next = newnode;:将链表头节点的 next 指针指向新的第一个节点,完成插入操作。

整个过程可以描述为:创建一个新节点,将其 next 指针指向原来的第一个节点,将原来的第一个节点 prev 指针指向新节点,将新节点 prev 指针指向链表头节点,最后将链表头节点的 next 指针指向新节点。这就将新节点插入到了链表的头部位置。

链表的尾插

//尾插
void PushBack(ListNode* phead,LTDataType x)
{
	assert(phead);
	ListNode* newnode = BuyNode(x);
	ListNode* tail = phead->prev;
	newnode->prev = tail;
	tail->next = newnode;
	phead->prev = newnode;
	newnode->next = phead;
}

 assert(phead);:使用 assert() 确认输入的链表头指针不为空。
ListNode* newnode = BuyNode(x);:使用函数 BuyNode() 创建值为 x 的新节点,该函数的实现不在此代码段中。
ListNode* tail = phead->prev;:将指针 tail 指向链表的最后一个节点,以备后续将新节点插入到最后一个位置。
newnode->prev = tail;:将新节点的 prev 指针指向原来的最后一个节点。
tail->next = newnode;:将原来的最后一个节点的 next 指针指向新节点。
phead->prev = newnode;:将链表头节点的 prev 指针指向新的最后一个节点,即使它成为了链表的尾部节点。
newnode->next = phead;:将新节点的 next 指针指向链表头节点,完成插入操作。
整个过程可以描述为:创建一个新节点,将其 prev 指针指向原来的最后一个节点,将原来的最后一个节点 next 指针指向新节点,将链表头节点的 prev 指针指向新的最后一个节点,最后将新节点的 next 指针指向链表头节点。这就将新节点插入到了链表的尾部位置。

链表的随机插入

void ListInsert(ListNode* phead,LTDataType pos,LTDataType x)
{
	assert(phead);
	ListNode* des = ListFind(phead,pos);
	ListNode* prev = des->prev;
	ListNode* newnode = BuyNode(x);
	newnode->next = des;
	des->prev = newnode;
	newnode->prev = prev;
	prev->next = newnode;
}

 assert(phead);:使用 assert() 确认输入的链表头指针不为空。
ListNode* des = ListFind(phead,pos);:使用函数 ListFind() 找到值为 pos 的节点,该函数的实现不在此代码段中。
ListNode* prev = des->prev;:将指针 prev 指向目标节点的前一个节点。
ListNode* newnode = BuyNode(x);:使用函数 BuyNode() 创建值为 x 的新节点,该函数的实现不在此代码段中。
newnode->next = des;:将新节点的 next 指针指向目标节点。
des->prev = newnode;:将目标节点的 prev 指针指向新节点。
newnode->prev = prev;:将新节点的 prev 指针指向目标节点的前一个节点。
prev->next = newnode;:将目标节点的前一个节点的 next 指针指向新节点。
整个过程可以描述为:通过查找函数 ListFind() 在链表中找到值为 pos 的节点 des,创建一个新节点,将其 next 指针指向目标节点 des,将目标节点 des 的 prev 指针指向新节点,将新节点 next 指针指向目标节点的前一个节点 prev,最后将前一个节点 prev 指针指向新节点。这就将新节点插入到了指定位置 pos 前面的位置。

链表的头删

//头删
void ListPopfront(ListNode* phead)
{
	assert(phead);
	assert(ListEmpty(phead));
	ListNode* first = phead->next;
	ListNode* second = first->next;

	phead->next = second;
	second->prev = phead;
}

 assert(phead);:使用 assert() 确认输入的链表头指针不为空。
assert(ListEmpty(phead));:使用 ListEmpty() 函数确认链表不为空,如果链表为空,则终止程序。
ListNode* first = phead->next;:将指针 first 指向链表的第一个节点,以备后续删除该节点。
ListNode* second = first->next;:将指针 second 指向链表中的第二个节点,以备后续将其和链表头连接。
phead->next = second;:将链表头的 next 指针指向第二个节点。
second->prev = phead;:将第二个节点的 prev 指针指向链表头,完成删除操作。
整个过程可以描述为:将链表头的 next 指针指向第二个节点,将第二个节点的 prev 指针指向链表头,即可将原来的第一个节点删除。

链表的尾删

//尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(ListEmpty(phead));
	ListNode* tail = phead->prev;
	ListNode* prevtail = tail->prev;
	free(tail);
	phead->prev = prevtail;
	prevtail->next = phead;
}

 assert(phead);:使用 assert() 确认输入的链表头指针不为空。
assert(ListEmpty(phead));:使用 ListEmpty() 函数确认链表不为空,如果链表为空,则终止程序。
ListNode* tail = phead->prev;:将指针 tail 指向链表的最后一个节点,以备后续删除该节点。
ListNode* prevtail = tail->prev;:将指针 prevtail 指向原先链表的倒数第二个节点,以备后续处理将其作为尾节点。
free(tail);:释放节点 tail 的内存空间。
phead->prev = prevtail;:将链表头的 prev 指针指向 prevtail,表示现在 prevtail 成为了新的尾节点。
prevtail->next = phead;:将 prevtail 的 next 指针指向链表头节点,即将原先的最后一个节点与链表头连接。
整个过程可以描述为:找到链表末尾的节点 tail,将其前一个节点 prevtail 作为新的尾节点,通过释放 tail 的内存空间将其从链表中删除,最后将新的尾节点 prevtail 和链表头连接起来。

链表的随机删除

void ListErase(ListNode* phead, LTDataType pos)
{
	assert(phead);
	assert(ListEmpty(phead));
	ListNode* tail = ListFind(phead,pos);
	ListNode* prev = tail->prev;
	ListNode* next = tail->next;

	prev->next = next;
	next->prev = prev;
	free(tail);
}

 assert(phead);:使用 assert() 确认输入的链表头指针不为空。
assert(ListEmpty(phead));:使用 ListEmpty() 函数确认链表不为空,如果链表为空,则终止程序。
ListNode* tail = ListFind(phead,pos);:使用 ListFind() 函数查找并返回值为 pos 的节点 tail,该函数的实现不在此代码段中。
ListNode* prev = tail->prev;:将指针 prev 指向目标节点 tail 的前一个节点。
ListNode* next = tail->next;:将指针 next 指向目标节点 tail 的后一个节点。
prev->next = next;:将指向目标节点 tail 的前一个节点的 next 指针指向目标节点 tail 的下一个节点 next。
next->prev = prev;:将目标节点 tail 的后一个节点的 prev 指针指向目标节点 tail 的前一个节点 prev。
free(tail);:释放目标节点的内存空间。
整个过程可以描述为:通过查找函数 ListFind() 在链表中找到值为 pos 的节点 tail,将其前一个节点 prev 和后一个节点 next 分别指向 tail 的前一个节点和后一个节点,最后通过释放 tail 的内存空间将其从链表中删除。

链表的销毁

//链表的销毁
void DestroyList(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	printf("链表销毁成功\n");
}

 assert(phead);:使用 assert() 确认输入的链表头指针不为空。
ListNode* cur = phead->next;:将指针 cur 指向链表的第一个节点,以备后续依次释放每一个节点的内存空间。
while (cur != phead):当 cur 不指向链表头节点时,执行以下代码块。
ListNode* next = cur->next;:将指针 next 指向当前节点的下一个节点,以备后续依次释放每一个节点的内存空间。
free(cur);:释放当前节点的内存空间。
cur = next;:将指针 cur 指向链表的下一个节点,以便继续删除链表的下一个节点。
free(phead);:释放链表头节点的内存空间。
printf("链表销毁成功\n");:输出提示信息,表示链表销毁成功。
整个过程可以描述为:从链表的第一个节点开始,通过循环依次释放每一个节点的内存空间,直到删除链表头节点为止。最后输出提示信息,表示链表销毁成功。

链表是否为空的判断

//判断链表是否为空
bool ListEmpty(ListNode* phead)
{
	assert(phead);
	return !(phead->next == phead);
}

 

assert(phead);:使用 assert() 确认输入的链表头指针不为空。
return !(phead-&gt;next == phead);:判断链表是否为空,如果 phead 的下一个节点指向链表头自身,则返回 false,表示该链表为空;否则返回 true,表示该链表不为空。

整个过程可以描述为:判断链表头节点 phead 的下一个节点是否指向链表头节点自身,如果是,则该链表为空,返回 false;否则说明该链表不为空,返回 true。
 

链表节点的创建 

//创建新的节点
ListNode* BuyNode(int x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	assert(newnode);
	newnode->val = x;
	newnode->prev = NULL;
	newnode->next = NULL;
	return newnode;
}

ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));:使用 malloc() 函数为新节点分配一段内存空间,并将分配的空间强制转换为 ListNode 类型的指针,保存在指针变量 newnode 中。
assert(newnode);:使用 assert() 确认内存分配是否成功,如果分配失败,则终止程序。
newnode->val = x;:将新节点的值设置为 x。
newnode->prev = NULL;:将新节点的前驱指针 prev 指向 NULL。
newnode->next = NULL;:将新节点的后继指针 next 指向 NULL。
return newnode;:返回新创建的节点的指针。
整个过程可以描述为:使用 malloc() 函数为新节点分配一段内存空间,并将新节点的值及前驱/后继指针初始化为 NULL,最后返回新节点的指针。

总结

介绍了带头双向链表的实现,实现了头插、头删、尾插、尾删、随机插入、随机删除、链表的创建、链表的销毁等功能。

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

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

相关文章

命令行更新Windows

命令行更新Windows powershell命令行更新安装 Windows Update module for Windows Powershell连接到 Windows Update 服务器并下载更新安装下载好的 Windows Update 更新 cmd执行Windows update更新检查更新下载 Windows Update 更新安装更新安装更新后重新启动设备 win10以下版…

Python中Pandas库的相关操作

目录 Pandas库 常用操作 创建DataFrame 查看数据 数据选择和过滤 数据排序和排名 缺失数据处理 数据聚合和分组 数据的合并和连接 Pandas库 Pandas是Python中常用的数据处理和分析库&#xff0c;它提供了高效、灵活且易于使用的数据结构和数据分析工具。 1.Series&a…

实例35---字符串反转,如将字符串 “www.runoob.com“ 反转为 “moc.boonur.www“。

文章目录 前言一、题目描述 二、题目分析 三、解题 1.解题方法一--- for循环来将字符的下标数值进行对每一个字符进行交换 ---程序运行代码 2.解题方法二------ 指针 ------ 程序运行代码(1)程序运行代码优化 总结 前言 本系列为C语言菜鸟100道基础经典例题详解刷题系列。点滴…

微服务:服务发现

1. 服务发现 eureka、nacos、Consul、etcd 和 zk 都是主流的服务发现工具&#xff0c;而 Dubbo 和 Polaris Mesh 则是基于服务发现的 RPC 框架。 它们的主要区别在于&#xff1a; eureka 是 Netflix 开源的一个服务发现组件&#xff0c;支持高可用和数据同步&#xff0c;具有…

如何使用Linkage Mapper揭示栖息地变化的故事

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Linkage Mapper解密数字世界链接 文章目录 引言1.1 定义和作用1.2 Linkage Mapper的历史和发展2.1 揭示栖息地变化的重要性2.2 Linkage Mapper的优势和不足

Windows下载安装RocketMq

Windows下载安装RocketMq 下载安装包启动NAMESERVER 和 BROKER启动NAMESERVER启动BROKER 安装可视化插件 下载安装包 RockitMQ官网下载地址&#xff1a;https://rocketmq.apache.org/release-notes/ 解压到指定的文件夹下 先配置JAVA_HOME 配置ROCKETMQ_HOME环境变量 …

使用Maple的Python API :OpenMaple(Windows下的解决方案)

在Maple 2023&#xff08;按照软件文档&#xff0c;Maple 2018及以上版本均适用&#xff1b;我目前测试的版本为2023&#xff09;的安装目录下&#xff0c;有软件附带的解释器&#xff0c;如D:\Program Files\Maple 2023\Python.X86_64_WINDOWS\Python.exe。这一Python解释器的…

Python中NumPy库的相关操作

目录 NumPy库 常用操作 创建数组 数组属性 索引和切片 数组运算 数组重塑 NumPy库 NumPy&#xff08;Numerical Python&#xff09;是Python中常用的数值计算库&#xff0c;它提供了高性能的多维数组对象和对数组进行操作的函数。 1.多维数组对象&#xff08;ndarray&…

Java 课程设计 —— 扫雷

一、团队课程设计博客链接&#xff1a; https://www.cnblogs.com/luomeili/p/10280310.html 二、个人负责模块或任务说明&#xff1a; 模块&#xff1a;文件操作 Minefield 类实现 三、自己的代码提交记录截图 四、自己负责模块或任务详细说明 1.Minefield 类实现 Minefie…

Java中的CountDownLatch和CyclicBarrier有什么作用?

在Java并发编程中&#xff0c;CountDownLatch和CyclicBarrier是两个非常有用的工具&#xff0c;它们可以帮助我们更加方便地进行线程通信和协作。在本文中&#xff0c;我将从面试的角度&#xff0c;详细讲解Java中的CountDownLatch和CyclicBarrier的概念、作用和实现方式&#…

基于卷积的图像分类识别(六):DenseNet FractalNet

系列文章目录 本专栏介绍基于深度学习进行图像识别的经典和前沿模型&#xff0c;将持续更新&#xff0c;包括不仅限于&#xff1a;AlexNet&#xff0c; ZFNet&#xff0c;VGG&#xff0c;GoogLeNet&#xff0c;ResNet&#xff0c;DenseNet&#xff0c;SENet&#xff0c;MobileN…

如何搭建自己的git服务器

GitHub&#xff0c;Gitee 想来大家都用过&#xff0c;我们的代码就是托管在这些平台上的。因此&#xff0c;你可能好奇为什么我们不自己搭建一个 git 呢服务器&#xff1f;下面&#xff0c;就开始教大家如何一步步搭建自己的 git 服务器&#xff08;试验成功的那一刻还是很让人…

Java 中 ArrayList 和 LinkedList 有什么区别

在Java中&#xff0c;ArrayList和LinkedList是两种常见的集合类。它们都实现了List接口&#xff0c;提供了类似数组的功能&#xff0c;可以存储任意类型的对象。虽然它们都可以实现相同的功能&#xff0c;但是它们的底层实现方式有所不同&#xff0c;因此在性能和用途上也存在一…

dom4j解析XML文件

主要为了讲解Mybatis中如何用dom4j解析XML,这里当作dom4j解析.XML文件的练习 引入mybatis配置文件和一个.xml文件 都是.xml <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN…

【C++】| 04——STL | 容器_vector

系列文章目录 【C】| 01——泛型编程 | 模板 【C】| 02——STL | 初识 【C】| 03——STL | 迭代器 【C】| 04——STL | 容器_vector 文章目录 1. vector容器2. vector库2.1 迭代器相关函数2.1 ww 1. vector容器 vector 与 动态数组 相似&#xff0c;可以自动调节自身大小。元素…

基于SpringBoot的美容院管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SpringBoot 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录 一、项目简介 二、系…

Windows10中英文切换按钮消失?一招解决

目录 问题场景&#xff1a; 问题描述 原因分析&#xff1a; 解决方案&#xff1a; 1. 打开设置&#xff0c;选择时间和语言 2. 进入日期时间设置 3. 进入高级键盘设置 4. 勾选这个勾选框&#xff0c;问题解决 问题场景&#xff1a; 博主玩道德与法治V在线模式时&#…

BGP防环,路由反射器,BGP联盟

数据的出口是路由的入口 ospf内部&#xff1a;10 ospf外部&#xff1a;150 静态路由&#xff1a;60 RIP&#xff1a;100 BGP&#xff1a;255 当下一跳是0.0.0.0 表示的是自己 display bgp peer //查看bgp邻居表 display bgp routing-table //查看bgp数据库 display i…

WPF MaterialDesign 初学项目实战(3)动态侧边栏

其他文章 WPF MaterialDesign 初学项目实战&#xff08;0&#xff09;:github 项目Demo运行 WPF MaterialDesign 初学项目实战&#xff08;1&#xff09;首页搭建 WPF MaterialDesign 初学项目实战&#xff08;2&#xff09;首页导航栏样式 创建侧边栏实体类 新建MenuBar文件…

Python动物图像分割API简单调用实例演示,阿里达摩院视觉智能开放平台使用步骤

阿里云视觉智能开放平台 - 动物分割 效果图演示平台入口创建获取密钥本地图片转 URL 与密钥测试代码调用演示语义分割知识拓展阿里云达摩院智能视觉开放平台 效果图演示 调用本地图片处理后可以直接保存到本地&#xff0c;右边就是分割好的效果图&#xff0c;可以看到分割的效…