数据结构之链表(带头双向循环链表)

news2025/1/17 3:58:39

文章目录

  • 前言
  • 一、带头双向循环链表
  • 二、双向链表的实现
    • 1.双向链表的定义
    • 2.双向链表的接口
    • 3.接口的实现
      • 创建返回链表的头结点
      • 创建一个新节点
      • 打印链表
      • 链表的销毁
      • 尾插
      • 尾删
      • 头插
      • 头删
      • 在链表中进行查找
      • 在pos前面插入数据
      • 链表删除pos位置处的节点
    • 4.主函数(测试)
  • 总结


前言

在了解了单链表之后,想必大家对于链表已经有了很多的了解,同时对于比单链表更有趣的带头双向循环链表也有了很大的兴趣。
因此今天要带大家了解的是链表中的带头双向循环链表。

一、带头双向循环链表

在这里插入图片描述
结合图片可以了解到,这种链表有头结点(哨兵位),每个节点带有两个指针,一个指向前一个节点,另一个指向后一个节点,这样就可以将前后节点都联系起来。虽然它的结构看上去有点复杂,但实际上的实现和使用都很简单,具体如何我们往下接着看。

二、双向链表的实现

1.双向链表的定义

typedef int LTDataType;
typedef struct ListNode//链表的节点
{
	LTDataType date;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

2.双向链表的接口

//创建链表的头结点(返回头结点的地址)
ListNode* ListCreate();
//打印链表
void ListPrint(ListNode* plist);
//链表的销毁
void ListDestory(ListNode* plist);
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x);
//链表的尾删
void ListPopBack(ListNode* plist);
//链表的头插
void ListPushFront(ListNode* plist, LTDataType x);
//链表的头删
void ListPopFront(ListNode* plist);
//链表的查找(如果找到了就返回下标,没找到就报错)
ListNode* ListFind(ListNode* plist, LTDataType x);
//在pos前面插入数据
void ListInsert(ListNode* pos, LTDataType x);
//双向链表删除pos位置处的节点
void ListErase(ListNode* pos);

3.接口的实现

创建返回链表的头结点

//创建返回链表的头结点
ListNode* ListCreate()//头结点(哨兵位)
{
	ListNode* p = (ListNode*)malloc(sizeof(ListNode));
	p->next = p;
	p->prev = p;
	return p;
}

这里有一个要注意点:我之前看的一些书上,对于头结点的date也进行了赋值,主要用于记录链表的长度(结点个数),但是实际上这种做法是不可取的。
原因如下:
链表节点的数据类型不确定;
如果类型为char,那它所存储的最大值是127,如果链表节点个数超过了127就会产生错误;
当然,即便是int类型,如果数据数量过多也会产生问题
所以,我们所定义的链表的头结点不进行赋值。

创建一个新节点

ListNode* ListBuyNewNode(LTDataType x)
{
	ListNode* p = (ListNode*)malloc(sizeof(ListNode));
	if (!p)
	{
		perror("malloc fail");//如果扩容失败,会报警告,告诉使用者扩容失败
	}
	p->next = p;
	p->prev = p;
	p->date = x;
	return p;
}

打印链表

void ListPrint(ListNode* plist)
{
	ListNode* p = plist;
	while (p->next != plist)//因为是循环链表,所以当p->next==plist为真时,p指向末尾节点(注意:不能用p-<next==NULL进行判断,会死循环)
	{
		printf("%d->", p->next->date);
		p = p->next;
	}
	printf("NULL\n");//如果链表为空则打印NULL
}

链表的销毁

void ListDestory(ListNode* plist)
{
	ListNode* tail = plist;
	while (plist->next != plist)
	{
		tail = plist->prev;
		plist->prev = tail->prev;
		plist->prev->next = plist;
		free(tail);
	}
	free(plist);
}

尾插

void ListPushBack(ListNode* plist, LTDataType x)
{
	ListNode* newnode = ListBuyNewNode(x);
	ListNode* tail = plist->prev;
	tail->next = newnode;
	newnode->prev = tail;
	plist->prev = newnode;
	newnode->next = plist;
}

尾删

void ListPopBack(ListNode* plist)
{
	ListNode* tail = plist;
	assert(plist->next != plist);//避免链表中没有数据仍在删除造成越界
	tail = plist->prev;
	plist->prev = tail->prev;
	plist->prev->next = plist;
	free(tail);
}

头插

void ListPushFront(ListNode* plist, LTDataType x)
{
	ListNode* newnode = ListBuyNewNode(x);
	newnode->prev = plist;
	newnode->next = plist->next;
	plist->next->prev = newnode;
	plist->next = newnode;
}

头删

void ListPopFront(ListNode* plist)
{
	assert(plist->next != plist);
	ListNode* begin = plist->next;
	plist->next = begin->next;
	begin->next->prev = plist;
	free(begin);
}

在链表中进行查找

(如果找到了就返回下标,没找到就返回NULL)

ListNode* ListFind(ListNode* plist, LTDataType x)
{
	ListNode* pos = plist->next;
	while (pos != plist)
	{
		if (pos->date == x)
			return pos;
		pos = pos->next;
	}
	exit(-1);//
}

查找也可以充当修改。

在pos前面插入数据

void ListInsert(ListNode* pos, LTDataType x)
{
	ListNode* newnode = ListBuyNewNode(x);
	newnode->next = pos;
	newnode->prev = pos->prev;
	pos->prev->next = newnode;
	pos->prev = newnode;
}

链表删除pos位置处的节点

void ListErase(ListNode* pos)
{
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
}

4.主函数(测试)

void test1()//测试尾插
{
	//创建返回链表的头结点
	ListNode* plist = ListCreate();
	// 双向链表尾插
	ListPushBack(plist, 10);
	ListPushBack(plist, 10);
	ListPushBack(plist, 10);
	ListPushBack(plist, 10);
	ListPrint(plist);
	//链表的销毁
	ListDestory(plist);
}
void test2()//测试尾删
{
	//创建返回链表的头结点
	ListNode* plist = ListCreate();
	ListPushBack(plist, 10);
	ListPushBack(plist, 10);
	ListPushBack(plist, 10);
	//链表的尾删
	ListPopBack(plist);
	ListPrint(plist);
	ListPopBack(plist);
	ListPrint(plist);
	ListPopBack(plist);
	ListPrint(plist);
	/*ListPopBack(plist);//再删除就会报错
	ListPrint(plist);*/
	//链表的销毁
	ListDestory(plist);
}
void test3()//测试头插
{
	//创建返回链表的头结点
	ListNode* plist = ListCreate();
	ListPushFront(plist, 20);
	ListPushFront(plist, 21);
	ListPushFront(plist, 25);
	ListPushFront(plist, 24);
	ListPushFront(plist, 22);
	ListPrint(plist);
	//链表的销毁
	ListDestory(plist);
}
void test4()//测试头删
{
	//创建返回链表的头结点
	ListNode* plist = ListCreate();
	ListPushFront(plist, 20);
	ListPushFront(plist, 21);
	ListPushFront(plist, 25);
	ListPushFront(plist, 24);
	ListPushFront(plist, 22);
	ListPopFront(plist);
	ListPrint(plist);
	ListPopFront(plist);
	ListPrint(plist);
	ListPopFront(plist);
	ListPopFront(plist);
	ListPrint(plist);
	ListPopFront(plist);
	ListPrint(plist);
	/*ListPopFront(plist);//再删除就报错
	ListPrint(plist);*/
	//链表的销毁
	ListDestory(plist);
}
void test5()//测试查找
{
	//创建返回链表的头结点
	ListNode* plist = ListCreate();
	ListPushFront(plist, 20);
	ListPushFront(plist, 21);
	ListPushFront(plist, 25);
	ListPushFront(plist, 24);
	ListPushFront(plist, 22);
	ListPrint(plist);
	ListNode*pos = ListFind(plist, 38);
	if (pos)
	{
		printf("找到了\n");
	}
	//链表的销毁
	ListDestory(plist);
}
void test6()//测试在pos位置之前插入一个节点
{
	//创建返回链表的头结点
	ListNode* plist = ListCreate();
	ListPushFront(plist, 20);
	ListPushFront(plist, 21);
	ListPushFront(plist, 25);
	ListPushFront(plist, 24);
	ListPushFront(plist, 22);
	ListPrint(plist);
	ListNode*pos = ListFind(plist, 22);
	ListInsert(pos,80);
	ListPrint(plist);
	//链表的销毁
	ListDestory(plist);
}
void test7()//测试删除pos位置的数据
{
	//创建返回链表的头结点
	ListNode* plist = ListCreate();
	ListPushFront(plist, 20);
	ListPushFront(plist, 21);
	ListPushFront(plist, 25);
	ListPushFront(plist, 24);
	ListPushFront(plist, 22);
	ListPrint(plist);
	ListNode*pos = ListFind(plist, 22);
	ListErase(pos);
	ListPrint(plist);
	//链表的销毁
	ListDestory(plist);
}
int main()
{
	//test1();
	//test2();
	//test3();
	//test4();
	//test5();
	test7();
	return 0;
}

总结

以上就是今天要讲的内容,本文主要介绍了带头双向循环链表,对带头双向循环链表的概念以及它的具体实现都进行了讲解。大家感兴趣的也可以根据作者所写思路自行实现带头双向循环链表。
本文作者目前也是正在学习数据结构的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,也希望可以多多支持作者,谢谢大家!

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

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

相关文章

vue-vuetify-admin案例讲解

vue-vuetify-admin案例讲解1. Introduction1.1 directory structure1.2 vue-cli1.3 vuex1.3.1 在store目录创建index.js1.3.2 在main.js中引入1.3.2 操作数据1.3.4 获取store中的值1.4 vue-router1.5 axios1.6 vuetify2. Code2.1 入门2.2 入门2.3 入门3. xxxvue-vuetify-admin:…

面试:常用的设计模式总结

一、Retrofit中的设计模式 二、OKHTTP中的设计模式 1、责任链模式interceptor拦截器&#xff1a;最主要的是5个拦截器 2、建造者&#xff1a;okhttp的client创建用了 3、观察者&#xff1a;返回结果抽离应该算是 4、工厂模式&#xff1a;Call 的创建用了工厂&#xff0c;Ca…

几何算法——10.欧拉操作

几何算法——10.欧拉操作1 欧拉操作1.1 欧拉操作的设计思想1.2 欧拉操作的选取1.3 几个典型的欧拉操作1.3.1 mvfs1.3.2 mev1.3.3 mef1.3.4 kemr1.3.5 kfmrh1.4 一个欧拉操作的实例1.5 欧拉操作的三点结论2. 非流形体2.1 非流形模型&#xff08;non-manifold model&#xff09;2…

动力节点索引优化解决方案学习笔记——性能分析

2. 性能分析 2.1 MySQL常见瓶颈 SQL中对大量数据进行比较、关联、排序、分组时CPU的瓶颈。 实例内存满足不了缓存数据或排序等需要&#xff0c;导致产生大量的物理IO。查询数据时扫描过多数据行&#xff0c;导致查询效率低。 2.2 Explain 使用EXPLAIN关键字可以模拟优化器执…

Super Vlan理论讲解

目录 Super Vlan作用 Super Vlan类型 Super Vlan通信规则 华为Super Vlan配置 传统Vlan部署中&#xff0c;一个Vlan对应一个网段和一个Vlanif接口来实现Vlan间的通信 造成了IP地址的浪费&#xff0c;因此提出了Super Vlan技术 Super Vlan作用 Super Vlan又称为聚合&#x…

java计算机毕业设计ssm金华学校社团管理系统

项目介绍 随着计算机信息技术的迅猛发展,互联网技术大规模应用到各行各业,传统的管理系统也逐渐精细化。高校作为教书育人的场所,各种管理也更应该智能化,特别是计算机信息专业更是最早接触信息技术,为高校各部门开发必要的系统是很有意义的事情。本金华学校社团管理系统对社团…

蓝桥杯刷题(二)

蓝桥杯刷题一.空间二.排序三.成绩分析四.蛇形填数五.跑步锻炼&#xff08;较难&#xff09;一.空间 这道题很简单&#xff0c;要弄清单位间的转换和如何输出就可以啦 #include <stdio.h>int main() {printf("%.0f",256/(32/4/2/1024.0000/1024));return 0; }记…

面试必学:输入 URL到页面的全过程-----五步完成、简单明了

目录 一、应用层解析 二、传输层连接 三、服务区处理 四、浏览器处理 五、断开 一、应用层解析 进行DNS解析&#xff1a;即将域名地址解析成 IP 地址 网络设备是通过 IP地址&#xff0c;作为身份标识 但是 IP地址不好记&#xff0c;所以很多时候就用一串单词 来进行表示。…

ipv6地址概述——深入讲解ipv6地址

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.ipv6地址深入了解 1.ipv6地址表示 ①冒号十六进制表示法&am…

计算机组成原理-中央处理器详细讲解(持续更新中)

CPU的功能和基本结构 CPU由运算器和控制器两大部分组成 CPU的功能 指令控制。完成取指令、分析指令和执行指令的操作&#xff0c;即程序的顺序控制。操作控制。一条指令的功能往往是由若干操作信号的组成来实现的。CPU管理并产生由内存取出的每条指令的操作信号&#xff0c;把…

FPGA学习笔记(八)同步/异步信号的打拍分析及处理

系列文章目录 一、FPGA学习笔记&#xff08;一&#xff09;入门背景、软件及时钟约束 二、FPGA学习笔记&#xff08;二&#xff09;Verilog语法初步学习(语法篇1) 三、FPGA学习笔记&#xff08;三&#xff09; 流水灯入门FPGA设计流程 四、FPGA学习笔记&#xff08;四&…

花十分钟用Python写了个蹭WiFi的软件,于是获取了隔壁单身妹子的WiFi试了试效果,居然发现...

Python写一个免费蹭WiFi的神器前因后果注意事项主要代码效果展示怎么学好Python&#xff1f;前因后果 昨晚十点学姐跟我发消息说她家的WiFi 不知道为什么今天就很慢&#xff0c;让我赶紧去她家帮她看看&#xff0c;当时我就怒了&#xff0c;这大晚上的我都要睡觉了还给我整这破…

Qt翻译(本地化)坑总结

文章目录坑1&#xff1a;无法生成ts文件坑2&#xff1a;ts文件的中文乱码坑3&#xff1a;不能直接翻译全局变量、静态变量、符号常量字符串官方文档 Internationalization with Qt 贴一个比较好的总结 Qt中&#xff0c;软件多语言国际化翻译的方法与步骤 坑1&#xff1a;无法生…

[Redis] Redis实战

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

[附源码]java毕业设计实践教学管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Echarts:好玩的timeline

Echarts是一个开源的可视化图表库&#xff0c;支持丰富的图表&#xff0c;官网中还有大量示例可以选择使用、参考。 其中比较有趣的一个特性是可以把数据随时间变化而变化&#xff0c;其效果与一些视频中比较不同国家的国力随时间变化的排名变化的效果相似。 接下啦我们就实现…

linux下golang环境安装教程(学习笔记)

linux下golang环境安装教程&#xff08;学习笔记&#xff09; SSH远程登录linux服务器 安装 mercurial包 [rootlocalhost ~]# yum install mercurial 安装git包 [rootlocalhost ~]# yum install git 安装gcc【一般自带安装好了的】 [rootlocalhost ~]# yum install gcc …

黑*头条_第3章_文章详情前后端成形记

黑*头条_第3章_文章详情前后端成形记 文章目录黑*头条_第3章_文章详情前后端成形记文章详情前后端成形记1 分布式主键封装1.1 依赖导入1.2 配置文件1.3 枚举封装1.4 序列封装1.5 Client封装1.6 Config封装1.7 Sequences封装1.8 使用案例1.9 扩展自增表2 App文章详情2.1 功能需求…

Spring IOC源码:registerBeanPostProcessors 详解

前言 上篇文章介绍了后置处理器BeanFactoryPostProcessor的注册、实例化及执行操作&#xff0c;这节介绍一下另外一个后置处理器BeanPostProcessor。前者是针对BeanFactory工厂对象进行增上改查操作&#xff0c;在bean实例化之前&#xff0c;我们可以修改其定义。后者是对实例…

电脑重装系统蓝屏是什么原因

​电脑蓝屏是由于系统故障、致命的系统错误或系统崩溃而导致的现象&#xff0c;要想修复电脑蓝屏&#xff0c;关键是找出原因所在。为此&#xff0c;下面小编就给大家整理电脑蓝屏是什么原因。 原因1、虚拟内存不足造成系统多任务运算错误&#xff1a; 虚拟内存是Windows系统所…