双向循环链表的讲解及实现(图解+代码/C语言)

news2024/9/27 5:54:17

本次为大家分享的是双向循环链表的增删查改等系列操作。

目录

一、图解双向循环链表结构

二、分步实现

(1)创建并初始化

(2)链表元素打印

(3)头插和尾插

(4)判断链表为空

(5)头删和尾删

(6)查找特定元素

(7)删除特定元素

(8)特定元素前插入

(9)链表销毁

三、优化及整体代码


一、图解双向循环链表结构

对于单向链表来说,每一个结点由数据块和一个指针域构成,只需要指针域记录下一个结点的位置即可。而双向链表则需要两个指针,对应“双向”,其结点具体结构和结点之间的连接方式如下。

二、分步实现

下面直接逐个上代码,给大家分块演示如何实现一个双向带头循环的链表。

(1)创建并初始化

首先我们需要声明结构体的结构,对于链表的初始化,我们只需要动态申请一个结点作为头结点,让这个哨兵结点头尾相连即可。

​​​​​​​

// 双链表 // Double Link List  // DLL

typedef int DLLData
typedef struct DLLNode
{
	DLLData data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;


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

(2)链表元素打印

链表的打印相对简单,我们只需要打印出其中的数据即可,为了表示是双向带头循环链表,不同结点之间的数据我们用“<=>”连接表示双向,用phead放在两端表示带头循环。

// 双向链表打印
void ListPrint(ListNode* phead)
{
	assert(phead);
	ListNode* tmp = phead->next;
	printf("phead<=>");
	while (tmp != phead)
	{
		printf("%d<=>", tmp->data);
		tmp = tmp->next;
	}
	printf("phead\n");
}

(3)头插和尾插

相比与之前学的单向链表,双向循环链表的头插和尾插则简单了许多,对照下面的图。我们传入函数的是哨兵结点phead, phead->next便是头结点,即在phead之后插入结点就是头插;同理,由于链表是循环的,因此在phead之前插入就是尾插。

// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* new_node = (ListNode*)malloc(sizeof(ListNode));
	new_node->data = x;
	new_node->next = phead->next;
	phead->next->prev = new_node;
	phead->next = new_node;
	new_node->prev = phead;
}


// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* new_node = (ListNode*)malloc(sizeof(ListNode));
	new_node->data = x;
	phead->prev->next = new_node;
	new_node->prev = phead->prev;
	phead->prev = new_node;
	new_node->next = phead;
}

(4)判断链表为空

完成了头插尾插,接下来就是头删和尾删了。但是在删除之前我们应该先检查一下我们的链表之中是否还存在结点,当没有结点的时候我们就无法继续删除了。所以我们需要检查一下链表是否为空,为空就是除哨兵节点外没有有效节点了,只有这哨兵结点循环,因此具体实现如下。

// 检查链表为空
bool CheckVoid(ListNode* rhs)
{
	assert(rhs);
	return rhs->next == rhs;
}

(5)头删和尾删

类比之前的头插和尾插,我们很轻松就可以找到头结点和尾结点,那么头删和尾删也就解决了。

// 双向链表头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(!CheckVoid(phead));
	ListNode* new_front = phead->next->next;
	free(phead->next);
	phead->next = new_front;
	new_front->prev = phead;
}

// 双向链表尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(!CheckVoid(phead));
	ListNode* new_tail = phead->prev->prev;
	free(phead->prev);
	phead->prev = new_tail;
	new_tail->next = phead;
}

(6)查找特定元素

和单链表一样我们只需要遍历整个链表即可,但是需要注意的是我们的链表有哨兵结点,而且是循环链表,因此我们需要从phead->next开始遍历,同时当我们遍历遇到phead时,说明已经找完了整个链表都没有对应元素,要在此设置退出值。

// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* tmp = phead->next;
	while (tmp != phead)
	{
		if (tmp->data == x)
			return tmp;
		tmp = tmp->next;
	}
	return NULL;
}

(7)删除特定元素

我们删除元素是基于查找函数完成的,首先通过查找函数找到对应结点,向删除函数传入该结点,由于我们是双向链表,因此只需要将找到的结点pos的前后结点连接并释放pos结点即可。

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
}

(8)特定元素前插入

在此选择的是在特定元素pos之前插入,想改成之后插入也十分简单,同样是和查找函数共同使用,找到之后传入pos结点和数据值即可。

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	ListNode* front = pos->prev;
	ListNode* new_node = BuyNewnode(x);
	front->next = new_node;
	new_node->next = pos;
	pos->prev = new_node;
	new_node->prev = front;
}

(9)链表销毁

链表销毁只需要遍历链表逐个释放即可,可以先释放其他节点然后跳出循环释放哨兵结点,也可以将phead->next设置为NULL,成为一个伪单链表,然后循环释放所有结点。此处使用后者实现。

// 双向链表销毁
void ListDestroy(ListNode* phead)
{
	assert(phead);
	ListNode* tmp = phead->next, *node = phead->next;
	phead->next = NULL;
	while (tmp)
	{
		node = tmp->next;
		free(tmp);
		tmp = node;
	}
}

三、优化及整体代码

当我们完成了插入函数的在之后,对于头插和尾插,直接调用函数即可,头插是在front前插入,尾插是在phead前插入。(函数声明和结构体声明等放在头文件中即可)

#include"Double ListNode.h"

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

// 创建一个新的结点
ListNode* BuyNewnode(LTDataType x)
{
	ListNode* new_node = (ListNode*)malloc(sizeof(ListNode));
	new_node->data = x;
	return new_node;
}

// 双向链表销毁
void ListDestroy(ListNode* phead)
{
	assert(phead);
	ListNode* tmp = phead->next, *node = phead->next;
	phead->next = NULL;
	while (tmp)
	{
		node = tmp->next;
		free(tmp);
		tmp = node;
	}
}

// 双向链表打印
void ListPrint(ListNode* phead)
{
	assert(phead);
	ListNode* tmp = phead->next;
	printf("phead<=>");
	while (tmp != phead)
	{
		printf("%d<=>", tmp->data);
		tmp = tmp->next;
	}
	printf("phead\n");
}

// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListInsert(phead, x);
}

// 双向链表尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(!CheckVoid(phead));
	ListErase(phead->prev);
}

// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListInsert(phead->next, x);
}

// 双向链表头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(!CheckVoid(phead));
	ListErase(phead->next);
}

// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* tmp = phead->next;
	while (tmp != phead)
	{
		if (tmp->data == x)
			return tmp;
		tmp = tmp->next;
	}
	return NULL;
}

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	ListNode* front = pos->prev;
	ListNode* new_node = BuyNewnode(x);
	front->next = new_node;
	new_node->next = pos;
	pos->prev = new_node;
	new_node->prev = front;
}

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
}

// 检查链表为空
bool CheckVoid(ListNode* rhs)
{
	assert(rhs);
	return rhs->next == rhs;
}

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

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

相关文章

MySQL调优-MySQL索引优化实战一

目录 MySQL调优-MySQL索引优化实战一 插入数据&#xff1a; 举一个大家不容易理解的综合例子&#xff1a; 1、联合索引第一个字段用范围不会走索引 2、强制走索引 什么是回表&#xff1f;为什么要回表&#xff1f;如何进行回表&#xff1f; 但是回表具有很大的弊端&#…

NetInside网络分析帮您解决系统性能问题(二)

前言 某大学信息中心负责人表示&#xff0c;有用户反馈&#xff0c;在通过VPN访问某一IP的80端口时连接时断时续。同时信息中心给到的信息是通过VPN&#xff1a;XXX.XXX.253.5访问IP地址XXX.XXX.130.200的80端口出现访问时断时续问题。 前一文章我们分析了系统整体性能分析&a…

学编程有哪些误区吗?避坑指南拿去不谢!

学习编程时信心满满&#xff0c;但反而效率不高&#xff0c;从“入门”到“放弃”&#xff0c;你肯定猜中了这些误区&#xff01; 今天就专门写了一篇避坑指南&#xff0c;提前避开误区&#xff0c;有助于更好学习编程。 误区1&#xff1a;忽略基础&#xff0c;好高骛远 现在…

@Column写在属性和写在get方法上面的区别

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是「奇点」&#xff0c;江湖人称 singularity。刚工作几年&#xff0c;想和大家一同进步&#x1f91d;&#x1f91d; 一位上进心十足的【Java ToB端大厂…

【YOLOv7/YOLOv5系列改进NO.51】融入多分支空洞卷积结构RFB-Bottleneck改进PANet构成新特征融合网络

文章目录前言一、解决问题二、基本原理三、​添加方法四、总结前言 作为当前先进的深度学习目标检测算法YOLOv7&#xff0c;已经集合了大量的trick&#xff0c;但是还是有提高和改进的空间&#xff0c;针对具体应用场景下的检测难点&#xff0c;可以不同的改进方法。此后的系列…

axios中get、post请求传参区别及使用

axios 发送请求时 params 和 data 的区别 params 中的参数是通过地址栏传参&#xff0c;一般用于get请求data 是添加到请求体&#xff08;body&#xff09;中的&#xff0c; 一般用于post请求get请求只能传query参数&#xff0c;query参数都是拼在请求地址上的post可以传body和…

qt使用qxlsx实现xlsx、xls表格文件快速写入和读取

一、前言 本片文章主要记录和分享一下qt使用qxlsx开源文件读写xlsx表格文件用法。 目录一、前言二、环境三、正文1.读取指定xlsx文件2.保存xlsx文件3.保存xlsx文件内容过大崩溃解决方案一4.保存xlsx文件内容过大崩溃解决方案二四、结语二、环境 windows linux qt5.7 三、正文…

【财务】FMS财务管理系统---对账平台

人工进行对账工作是非常繁杂的&#xff0c;此时&#xff0c;就非常有必要建设一个对账平台。笔者在本文介绍了对账平台的相关内容&#xff0c;分享给大家。 前面介绍过应收对账、财务应付结算两部分内容&#xff1b;应收对账主要是调用第三方支付的接口获取支付流水信息与我司的…

C++设计模式:三种工厂模式详解(简单工厂,工厂模式,抽象工厂)

文章目录简单工厂模式简单工厂实现步骤简单工厂优缺点工厂模式工厂模式和简单工厂模式有什么不同&#xff1f;工厂模式实现步骤实现代码工厂模式优缺点抽象工厂模式抽象工厂模式实现步骤实现代码抽象工厂模式优缺点简单工厂模式 简单工厂模式属于类的创建型模式,又叫做静态工厂…

【算法】算法分析技术(第一章习题解答)

1 算法分析技术 1.1 假设 fff 和 ggg 是定义在自然数集合上的函数, 若对某个其他函数 hhh 有 fO(h)f O(h)fO(h)和 gO(h)g O(h)gO(h) 成立, 那么证明 fgO(h)f g O(h)fgO(h) 证明&#xff1a; 根据已知条件 fO(h)f O(h)fO(h)&#xff0c;存在 c1>0c_{1}>0c1​>0 …

错题记录2:源码补码二进制转换

补码原码二进制一直是本人比较头疼的问题&#xff0c;今日找题目来做发现还是错得多&#xff0c;记录以下&#xff0c;再回顾一遍。 1.如下代码输出的是什么&#xff08; &#xff09; char a101; int sum200; a27;suma; printf("%d\n",sum); A: 327 B: 99 C: 3…

【MySQL】详解MySQL数据库事务

MySQL数据库事务数据库事务特性事务的并发问题事务的隔离级别分布式事务解决方案1.XA 协议2.TCC3.消息一致性方案4.阿里云中的全局事务服务 GTS数据库事务特性 数据库事务具备ACID四大特性&#xff1a; 原子性&#xff1a;是指事务操作时具备原子操作的&#xff0c;就是说整…

分布式系统架构

分布式系统定义 分布式系统&#xff1a;硬件或软件组件分布在不同的网络计算机上&#xff0c;彼此之间仅仅通过消息传递进行通信和协调的系统。 为什么需要分布式系统 提升系统吞吐量&#xff1a;集群协同解决单机性能瓶颈提高系统可用性 &#xff1a;冗余部署&#xff0c;各…

OSS阿里云存储

一、开通“对象存储OSS”服务 2、进入管理控制台 二、控制台使用 点击Buket列名&#xff0c;Buket可以看作是一系列的虚拟内存空间它是一个独立的小个体 创建mystudent 创建成功后就有下面的样子了 &#xff0c;创建的时候读写权限&#xff0c;我们选择公共读&#xff0c…

matlab中sign函数的使用(提取符号)

仅用于记录自己学习过程中遇到的函数 matlab中sign函数的使用&#xff0c;提取符号 一、语法 Y sign(x) 返回与 x 大小相同的数组 Y&#xff0c;其中 Y 的每个元素是&#xff1a; 1&#xff0c;前提是 x 的对应元素大于 0。 0&#xff0c;前提是 x 的对应元素等于 0。 -1&am…

CAP定理与分布式事务理论

文章目录一、CAP定理1.1 一致性1.2 可用性1.3 分区容错1.4 定理的矛盾点二、分布式事务理论2.1 BASE理论2.2 解决分布式事务思路一、CAP定理 分布式系统有三个指标。 Consistency&#xff08;一致性&#xff09;Availability&#xff08;可用性&#xff09;Partition toleranc…

GBASE斩获2022科技赋能金融业数字化转型突出贡献奖

12月27日&#xff0c;由中国人民银行主管的《金融电子化》杂志社主办的“2022中国金融科技年会暨第十三届金融科技应用创新奖颁奖典礼”成功举办&#xff0c;在本次活动上&#xff0c;GBASE南大通用“多模多态分布式数据库GBase 8c”&#xff0c;荣获“2022科技赋能金融业数字化…

PyCharm高级配置

pycharm设置内存大小步骤1&#xff0c;Help -> FindAction -> (输入 “VM”) -> (点击)“Edit Custom VM options”2&#xff0c;Pycharm会在编辑器中打开适当的vmoptions文件&#xff08;pycharm.vmoptions或pycharm64.options&#xff09;。3&#xff0c;将**-Xms**…

CCIE重认证350-401

拖图题 QoS traffic policing: causes TCP retransmissions when traffic is dropped导致TCP重传时流量下降 introduces no delay and jitter引入无延迟和抖动 drops excessive traffic减少过多的流量 traffic shaping: buffers excessive traffic缓冲过多的流量 introduce…

Sensor+ISP专栏-Demosaic

SensorISP专栏-Demosaic 1.what is raw raw图指的是从cmos sensor直接输出的原始图像&#xff0c;它本身没有颜色分量。在CMOS Sensor中使用彩色滤镜阵列(color filter array&#xff0c;CFA) 的方式捕获彩色图像&#xff0c;每个感光像素的上面覆盖一个语物理像素相同面积大…