数据结构线性表——带头双向循环链表

news2024/10/7 6:45:40

前言:小伙伴们好久不见啦,上篇文章我们一起学习了数据结构线性表其一的单链表,了解了单链表的不少好处,但是不可能有完美的数据结构,就算是单链表,也会有很多缺点。

那么今天这篇文章,我们就来学习单链表的promax版本——带头双向循环链表


一.什么是带头双向循环链表

关于带头双向循环链表,我们将它拆分为带头、双向、循环、链表四个部分,其中链表我们已经知道是怎么回事了,那我们就来一起结合下图分析前三个概念。

1.带头 

        所谓带头,也就是在链表的开头处,有一个不存放任何数据的头节点,我们通常称其为“哨兵位”。

        那么哨兵位存在的意义是什么呢???

        它可以帮助我们更方便的进行对链表的各种操作。具体好在哪里,我们结合后边实现链表的各种操作来进行展示。

2.双向

        我们前边学习过的单链表,它的每个节点之间只有一条链子相连,并且只能由前一个节点去找到后一个节点

        而双向链表,也就是两个节点之间有两条链子相连,不仅能从前一个找到后一个,也能从后一个去找到前一个

3.循环

        循环,顾名思义,就是将链表的头尾也进行连接,形成一个逻辑意义上的环形链表。

那么理解完带头双向循环链表的含义之后,我就就一起来看看到底来如何实现它吧。

此后我们将该链表的名字简化为双链表


二.双链表的实现

1.双链表的定义

typedef int DLLDataType;
//定义双链表
typedef struct DLinkList
{
	DLLDataType data;
	struct DLinkList* prev;//指向前一个节点
	struct DLinkList* next;//指向后一个节点
}DLLNode;

双链表是在单链表的基础上,比它多出一个prev指针去指向前一个节点,还是比较容易理解的。


2.双链表的初始化

//初始化双链表
DLLNode* DLinkListInit()
{
	DLLNode* phead = (DLLNode*)malloc(sizeof(DLLNode));
	if (phead == NULL)
	{
		perror("DLinkListInit->malloc");
	}
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

双链表的初始化需要先造出哨兵位考虑到链表为空,并且链表还要循环,所以我们将哨兵位的prev和next都指向自己

    DLLNode* dll = DLinkListInit();

创建一个双链表,我们习惯于运用上述方式。

因为如果用单链表的初始化方式,我们需要用到二级指针,但是我们后续双链表各种功能的操作,完全不和二级指针沾边

所以为了让我们的双链表全部由一级指针完成,选择采用接收函数返回值的方式来创建双链表


3.双链表节点的创建

DLLNode* CreateNewNode(DLLDataType x)
{
	DLLNode* newnode = (DLLNode*)malloc(sizeof(DLLNode));
	if (newnode == NULL)
	{
		perror("CreateNewNode->malloc");
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

双链表创建新节点就和单链表差不多啦,要注意的就是不要忘记两个指针置空,防止出现野指针

这样,我们就实现了一个基本的双链表框架,下面来实现双链表的各种基础操作。


 三.双链表的操作

1.双链表的打印

那么为了方便其他功能的测试,我们还是先来实现双链表的打印功能:

void DLinkListPrint(DLLNode* phead)
{
	assert(phead);
	DLLNode* cur = phead->next;
	printf("phead<=>");
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("phead\n");
}

我们还是严格的进行一下assert断言如果phead为空,就说明双链表不存在

这里要注意两点:

1.cur为什么是phead->next???

        不难理解,我们在双链表初始化的时候,给到dll的返回值是哨兵位,但是哨兵位不存储数据,所以要从哨兵位的下一个节点开始。

2.while循环的判断条件

        因为我们是一个可循环的链表,所以并不存在cur为空的情况,但是cur最后会重新指向哨兵位,所以当cur == phead时,说明我们已经将双链表遍历一遍了

至于printf函数的内容,只是为了好看哈哈,展示一下:

这样能够让大家更形象的认识双链表。


2.双链表的尾插

双链表的尾插相较于单链表有什么优势呢???

单链表想尾插,首先要进行循环找尾时间复杂度就高了,但是双链表就好办,因为哨兵位的前一个节点就是尾,也就是phead->prev,尾找到之后,就好办了:

//尾插
void DLinkListPushBack(DLLNode* phead, DLLDataType x)
{
	assert(phead);
	DLLNode* newnode = CreateNewNode(x);
	DLLNode* tail = phead->prev;
	tail->next = newnode;
	newnode->next = phead;
	newnode->prev = tail;
	phead->prev = newnode;
}

用tail代替尾,接下来的一顿操作,就是:

旧尾的next指向新尾

新尾的next指向哨兵位

新尾的prev指向旧尾

哨兵位的prev指向新尾

看起来很简单,但是我们知道,单链表必须得考虑一下链表是否为空的特例,但是双链表不需要

因为双链表如果为空,那就只有哨兵位,哨兵位自己的头尾相连,带入上述代码操作之后,不会有任何错误。


 3.双链表的尾删

尾删就更简单了,只需要找到尾,再通过尾找到尾的前一个节点,再让此节点和哨兵位互连,再将尾free即可:

//尾删
void DLinkListPopBack(DLLNode* phead)
{
	assert(phead);
	DLLNode* tail = phead->prev;
	DLLNode* tailprev = tail->prev;
	phead->prev = tailprev;
	tailprev->next = phead;
	free(tail);
	tail = NULL;
}

尾删要考虑只有一个节点的特例吗,依然不用,因为就算是空链表,进行一顿操作之后,还是让哨兵位自己头尾相连

到这里,小伙伴们是否已经感受到了哨兵位,以及双链表的强势之处啦


4.双链表的头插

头插就和尾插差不多了,这里我直接给出代码,希望小伙伴们可以自己理解掌握哦。

//头插
void DLinkListPushFront(DLLNode* phead, DLLDataType x)
{
	assert(phead);
	DLLNode* head = phead->next;
	DLLNode* newnode = CreateNewNode(x);
	phead->next = newnode;
	newnode->next = head;
	head->prev = newnode;
	newnode->prev = phead;
}

5.双链表的头删

头删也和尾删类似:

//头删
void DLinkListPopFront(DLLNode* phead)
{
	assert(phead);
	DLLNode* head = phead->next;
	DLLNode* headnext = head->next;
	phead->next = headnext;
	headnext->prev = phead;
	free(head);
	head = NULL;
}

6.双链表的查找

如果是查找的话,那我们还得老老实实的从头遍历:

//查找
DLLNode* DLinkListFind(DLLNode* phead,DLLDataType x)
{
	assert(phead);
	DLLNode* cur = phead->next;
	while(cur)
	{
		if (cur->data == x)
			return cur;
		else
			cur = cur->next;
	}
	return NULL;
}

还是要注意这里while循环的条件,和双链表的打印一样


7.双链表的任意插

双链表的任意位置的插入依然要和查找连用,因为只有查找才能得到pos位置的地址

但是我们这里规定一下,任意插就是pos位置前插

比如说我想在表的第四个位置插入新数据,那我就要把第四个位置空出来,让原来的第四位以及他后边的都老老实实往后退

这样一来,我们就需要找到pos节点的前一个节点,这样方便我们进行操作:

//pos位置插
void DLinkListInsert(DLLNode* pos, DLLDataType x)
{
	assert(pos);
	DLLNode* newnode = CreateNewNode(x);
	DLLNode* posprev = pos->prev;
	posprev->next = newnode;
	newnode->prev = posprev;
	pos->prev = newnode;
	newnode->next = pos;
}

8.双链表的任意删

任意删的形式就和任意插差不多,只是还需要另外记录pos的下一个节点

//pos位置删
void DLinkListEease(DLLNode* pos)
{
	assert(pos);
	DLLNode* posprev = pos->prev;
	DLLNode* posnext = pos->next;
	posprev->next = posnext;
	posnext->prev = posprev;
	free(pos);
    pos = NULL;
}

9.双链表的修改

想要修改数据,还是要用查找操作来找到要修改pos位置的地址,而后就简单了:

//pos位置改
void DLinkListAmend(DLLNode* pos, DLLDataType x)
{
	assert(pos);
	pos->data = x;
}

直接修改data即可。


10.双链表的销毁

双链表的销毁,同样是需要遍历对个个空间进行free,值得注意的是,哨兵位也需要销毁

//销毁
void DLinkListDestroy(DLLNode* phead)
{
	assert(phead);
	DLLNode* cur = phead->next;
	while (cur != phead)
	{
		DLLNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}

四.完整代码展示

1.DLinkList.h

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

typedef int DLLDataType;
//定义双链表
typedef struct DLinkList
{
	DLLDataType data;
	struct DLinkList* prev;
	struct DLinkList* next;
}DLLNode;

//初始化双链表
DLLNode* DLinkListInit();
//打印双链表
void DLinkListPrint(DLLNode* phead);
//创造新节点
DLLNode* CreateNewNode(DLLDataType x);
//尾插
void DLinkListPushBack(DLLNode* phead, DLLDataType x);
//尾删
void DLinkListPopBack(DLLNode* phead);
//头插
void DLinkListPushFront(DLLNode* phead, DLLDataType x);
//头删
void DLinkListPopFront(DLLNode* phead);
//查找
DLLNode* DLinkListFind(DLLNode* phead,DLLDataType x);
//pos位置插
void DLinkListInsert(DLLNode* pos, DLLDataType x);
//pos位置删
void DLinkListEease(DLLNode* pos);
//pos位置改
void DLinkListAmend(DLLNode* pos,DLLDataType x);
//销毁
void DLinkListDestroy(DLLNode* phead);

2.DLinkList.c

#include "DLinkList.h"
//初始化双链表
DLLNode* DLinkListInit()
{
	DLLNode* phead = (DLLNode*)malloc(sizeof(DLLNode));
	if (phead == NULL)
	{
		perror("DLinkListInit->malloc");
	}
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
//打印双链表
void DLinkListPrint(DLLNode* phead)
{
	assert(phead);
	DLLNode* cur = phead->next;
	printf("phead<=>");
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("phead\n");
}
//创造新节点
DLLNode* CreateNewNode(DLLDataType x)
{
	DLLNode* newnode = (DLLNode*)malloc(sizeof(DLLNode));
	if (newnode == NULL)
	{
		perror("CreateNewNode->malloc");
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}
//尾插
void DLinkListPushBack(DLLNode* phead, DLLDataType x)
{
	assert(phead);
	DLLNode* newnode = CreateNewNode(x);
	DLLNode* tail = phead->prev;
	tail->next = newnode;
	newnode->next = phead;
	newnode->prev = tail;
	phead->prev = newnode;
}
//尾删
void DLinkListPopBack(DLLNode* phead)
{
	assert(phead);
	DLLNode* tail = phead->prev;
	DLLNode* tailprev = tail->prev;
	phead->prev = tailprev;
	tailprev->next = phead;
	free(tail);
	tail = NULL;
}
//头插
void DLinkListPushFront(DLLNode* phead, DLLDataType x)
{
	assert(phead);
	DLLNode* head = phead->next;
	DLLNode* newnode = CreateNewNode(x);
	phead->next = newnode;
	newnode->next = head;
	head->prev = newnode;
	newnode->prev = phead;
}
//头删
void DLinkListPopFront(DLLNode* phead)
{
	assert(phead);
	DLLNode* head = phead->next;
	DLLNode* headnext = head->next;
	phead->next = headnext;
	headnext->prev = phead;
	free(head);
	head = NULL;
}
//查找
DLLNode* DLinkListFind(DLLNode* phead,DLLDataType x)
{
	assert(phead);
	DLLNode* cur = phead->next;
	while(cur)
	{
		if (cur->data == x)
			return cur;
		else
			cur = cur->next;
	}
	return NULL;
}
//pos位置插
void DLinkListInsert(DLLNode* pos, DLLDataType x)
{
	assert(pos);
	DLLNode* newnode = CreateNewNode(x);
	DLLNode* posprev = pos->prev;
	posprev->next = newnode;
	newnode->prev = posprev;
	pos->prev = newnode;
	newnode->next = pos;
}
//pos位置删
void DLinkListEease(DLLNode* pos)
{
	assert(pos);
	DLLNode* posprev = pos->prev;
	DLLNode* posnext = pos->next;
	posprev->next = posnext;
	posnext->prev = posprev;
	free(pos);
	pos = NULL;
}
//pos位置改
void DLinkListAmend(DLLNode* pos, DLLDataType x)
{
	assert(pos);
	pos->data = x;
}
//销毁
void DLinkListDestroy(DLLNode* phead)
{
	assert(phead);
	DLLNode* cur = phead->next;
	while (cur != phead)
	{
		DLLNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}

测试代码大家自行进行测试,这里就不在进行展示啦。


五.总结

双链表相比于单链表还是有很大优势的,建议大家在学习过单链表的基础上完全靠自己的写一写双链表,这将会让你对链表知识的掌握更上一层楼!

最后还是提醒大家不要忘记一键三连哦!!!

我们下期再见啦!

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

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

相关文章

全网最细,Apipost接口自动化测试-关联配置,老鸟带你上高速...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 在接口自动化测试…

Arduino到底适不适合做产品

文章目录 一、Arduino性能很低&#xff0c;不如树莓派等开发板&#xff0c;所以不要用Arduino做开发二、Arduino程序效率很低&#xff0c;所以不要用Arduino做开发三、Arduino只能开发玩具&#xff0c;不能做产品四、Arduino开发板成本太高&#xff0c;不适合做产品总结个人见解…

iPhone或在2024开放第三方应用商店。

iPhone或开放第三方应用商店&#xff0c;可以说这是一个老生常谈的话题。对于像是iOS这样封闭的系统来说&#xff0c;此前传出苹果可能开放侧载消息的时候&#xff0c;又有谁能信&#xff0c;谁会信&#xff1f; 如果是按照苹果自身的意愿&#xff0c;这种事情自然是不可能发生…

【LeetCode笔试题】88.合并两个有序数组

问题描述 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 注意&#xff1a;最终&#xff0c;合…

王学岗visibility改变后调用onLayout()

自定义控件的时候发现了一个bug。 Button位移动画执行结束后我设置了一个不相关的TextView的可见性由gone变为visible.令人郁闷的是&#xff0c;只要我注释的地方放开。动画执行结束后button都会重新绘制在位移动画开始的位置。注释掉这段代码就正常。 经过分析后得知 View的Vi…

python OrderedDict类(有序字典)

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 创建有序字典 import collectionsdic collections.OrderedDict() dic[k1] v1 dic[k2] v2 dic[k3] v3 print(dic)#输出&#xff1a;OrderedDict([(k1, v1), (…

Vatee万腾科技决策力的未来展望:开创数字化创新的新高度

随着科技不断演进&#xff0c;Vatee万腾的科技决策力在数字化创新领域展现出了强大的潜力和前瞻性。 Vatee万腾的科技决策力被视为数字化创新的引擎&#xff0c;为未来创新注入了新的动力。通过深刻的市场洞察和科学决策&#xff0c;Vatee万腾致力于推动数字化创新走向新的高度…

图论11-欧拉回路与欧拉路径+Hierholzer算法实现

文章目录 1 欧拉回路的概念2 欧拉回路的算法实现3 Hierholzer算法详解4 Hierholzer算法实现4.1 修改Graph&#xff0c;增加API4.2 Graph.java4.3 联通分量类4.4 欧拉回路类 1 欧拉回路的概念 2 欧拉回路的算法实现 private boolean hasEulerLoop(){CC cc new CC(G);if(cc.cou…

可视化 | 3D文字球状标签云

文章目录 &#x1f4da;改编点&#x1f4da;final 改编自echarts 3d词云&#xff08;指向滑动、拖动、缩放、点击、自转 &#xff09; &#x1f4da;改编点 背景透明&#xff1a;background:rgb(0,0,0,0);不用链接&#xff0c;用span&#xff0c;重点span标class"star&q…

2023年成为优秀自动化测试工程师的 7 个步骤!

“测试自动化测试工程师可以将你从充满代码的世界中拯救出来。”企业完全同意这一说法&#xff0c;这就是您在自动化测试行业中看到大量就业机会的原因。我在 Quora 上收到了很多与自动化测试中的职业选择相关的答案请求&#xff0c;以及人们如何在有或没有手动测试经验的情况下…

Pytorch从零开始实战09

Pytorch从零开始实战——YOLOv5-Backbone模块实现 本系列来源于365天深度学习训练营 原作者K同学 文章目录 Pytorch从零开始实战——YOLOv5-Backbone模块实现环境准备数据集模型选择开始训练可视化模型预测总结 环境准备 本文基于Jupyter notebook&#xff0c;使用Python3.…

PyBind11五分钟入门【Python/C++调用】

从 Python 调用 C 基本上有两种方法&#xff1a;使用 PyBind11 C 库生成 Python 模块&#xff0c;或使用 cytpes Python 包访问已编译的共享库。 使用 PyBind11 我们可以更轻松地共享许多数据类型&#xff0c;而使用 ctypes 是一种低级 C 风格的解决方案。 在线工具推荐&#x…

synchronized的原理和Callable接口

目录 ♫synchronized原理 ♪锁升级 ♪锁优化 ♫Callable接口 ♫synchronized原理 我们知道synchronized锁可以控制多个线程对共享资源的访问&#xff0c;两个线程针对同一变量访问就会产生阻塞等待。而synchronized锁并不是一成不变的&#xff0c;它会根据情况进行一次升级。…

MySQL---存储过程

存储过程的相关概念 是一组为了完成特定功能的sql语句的集合&#xff0c;类似于函数 写好了一个存储过程之后&#xff0c;我们可以像函数一样随时调用sql的集合。 复杂的&#xff0c;需要很多sql语句联合执行完成的任务 存储过程再执行上比sql语句的执行速度更快&#xff0c…

CS224W5.2——Relational and Iterative Classification

本节中&#xff0c;我们介绍用于节点分类的关系分类器和迭代分类。 从关系分类器开始&#xff0c;我们展示了如何基于邻居的标签迭代更新节点标签的概率。然后讨论迭代分类&#xff0c;通过根据邻居的标签及其特征预测节点标签来改进集体分类。 文章目录 1. 框架2. 关系分类3.…

基于SpringBoot的SSMP整合案例(开启日志与分页查询条件查询功能实现)

开启事务 导入Mybatis-Plus框架后&#xff0c;我们可以使用Mybatis-Plus自带的事务&#xff0c;只需要在配置文件中配置即可 使用配置方式开启日志&#xff0c;设置日志输出方式为标准输出mybatis-plus:global-config:db-config:table-prefix: tb_id-type: autoconfiguration:…

【黑客】最适合小白的学习顺序

一、黑客是什么 原是指热心于计算机技术&#xff0c;水平高超的电脑专家&#xff0c;尤其是程序设计人员。但后来&#xff0c;黑客一词已被用于泛指那些专门利用电脑网络搞破坏或者恶作剧的家伙。 二、学习黑客技术的原因 其实&#xff0c;网络信息空间安全已经成为海陆空之…

Python基础教程:类--继承和方法的重写

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 什么是继承 继承就是让类与类之间产生父子关系&#xff0c;子类可以拥有父类的静态属性和方法 继承就是可以获取到另一个类中的静态属性和普通方法&#xff08;并非所有成员&#xff09; 在python中&#xff0c;新建的类可…

【紫光同创国产FPGA教程】——【PGL22G第十一章】以太网传输实验例程

本原创教程由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注www.meyesemi.com) 适用于板卡型号&#xff1a; 紫光同创PGL22G开发平台&#xff08;盘古22K&#xff09; 一&#xff1a;盘古22K开发板&#xff08;紫光…

建筑能源管理(9)——公共建筑能源管理技术

现阶段&#xff0c;在我国经济高速发展的同时&#xff0c;也面临着资源有限、能源消费急剧增长、能源供给与需求之间的矛盾日益突出等问题。数据显示&#xff0c;现阶段我国单位GDP的能耗水平是发达国家的3倍左右&#xff0c;这正是能源总体利用率较低所造成的。建筑能耗作为我…