【数据结构】【版本1.2】【线性时代】——链表之王(双向带头循环)

news2024/11/25 18:54:11

目录

引言 

链表的分类 

双向链表的结构

双向链表的实现 

定义

创建新节点 

初始化 

打印 

尾插

头插 

判断链表是否为空

尾删 

头删

查找与修改 

指定插入

指定删除 

销毁 

顺序表和双向链表的优缺点分析

双向链表oj题 

源代码 

dlist.h

dlist.c

test.c


个人专栏:《数据结构世界》


引言 

数据结构之路在链表章节,前面介绍过单链表,今天我们来介绍最复杂的链表——双向链表(Double Linked List) 

数据结构世界已经有顺序与链式两种力量,随着时间的推移,链式的力量居然迎来了进化,从单链表进化成链表之王——双向带头循环链表,从此拥有更为强大的神通——轮回与空间回溯

一、链表的分类 

虽然有这么多的链表的结构,但是我们实际中 最常用 还是 两种结构 单链表 双向带头循环链表
  1.  无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  2.  带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

 前面我们讲的单链表,就是无头单向非循环链表,而现在讲的双向链表,就是带头双向循环链表。

二、双向链表的结构

注意: 这里的 “带头” 跟前面我们说的“头节点”是两个概念,实际前面的在单链表阶段称呼不严谨,但是为了同学们更好的理解就直接称为单链表的头节点。
  • 带头链表里的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这里“放哨的”
  • “哨兵位”存在的意义: 遍历循环链表避免死循环

三、双向链表的实现 

3.1 定义

与单链表不同,一个节点里有两个指针,分别指向前节点后节点,实现双向  

3.2 创建新节点 

创建新节点与单链表大致相同,抽离成函数方便创建节点 

3.3 初始化 

双向链表与单链表很大区别,就是在于初始化创建哨兵位,而哨兵位不存储有效数据,所有这里存入-1,并让prev和next指针都指向自身 

3.4 打印 

首先assert断言,因为phead指向哨兵位,一定不为空,其次因为双向循环链表里没有NULL,所有停止条件就看哨兵位,当cur指针的下一个为哨兵位时,就代表走到尾部了 

3.5 尾插

双向循环结构在空链表插入时,带来了极大的便利。因为prev指针的存在,使得找尾十分方便,不用遍历链表,直接访问哨兵位的上一个节点即可 

如果是单链表,是不是还要讨论空链表的特殊情况啊?但是,在双向循环链表中,上述代码就可包含所有情况。因为哨兵位的存在,使得链表一定不为空。 

 

同时,因为哨兵位的存在,我们不用传二级指针了,只用对结构体进行操作。怎么样,有没有发现双向链表的巨大优势?  

运行结果

 

3.6 头插 

头插时,要注意的就是要先链接后一个节点再链接前一个节点 

运行结果

3.7 判断链表是否为空

删除值得注意的是,不能删除哨兵位,要不然后续都无法对链表进行操作,所以专门写个函数来判断除了哨兵位链表是否为空,再用assert断言返回值   

如果phead和phead->next相等则为空,返回1,即为真 ;不相等,则不为空,返回0,即为假 

3.8 尾删 

经历过单链表,双向链表的尾删就显得太简单了。找尾tail直接往phead的上一位,同时创建tailPrev,在释放尾部节点后,双向链接哨兵位 


运行结果 

3.9 头删

同样,双向链表的头删也很简单。 找头first往phead的下一位,再创建second,在释放头部空间后,双向链接哨兵位 

运行结果 

3.10 查找与修改 

双向链表的查找,找到返回地址,找不到返回NULL。要注意的是从哨兵位的下一位开始找,因为哨兵位是不存储有效数据的,直到重新找到哨兵位 

运行结果 

查找到地址后,就可以对其解引用访问,进行修改 

3.11 指定插入

 在pos前插入

在双向链表,找到pos的上一个节点的地址prev太简单了  

运行结果 

在这里可以复用指定插入函数,快速实现头插和尾插  

头插 

尾插

3.12 指定删除 

创建posPrev和posNext两个指针,释放掉pos的节点空间,再相互链接  

在pos删除 

运行结果 

在这里也可以复用指定删除函数,快速实现头删和尾删   

头删 

尾删

大家有没有发现,因为双向链表存在哨兵位链表不为空省去了很多关于空链表和空指针的讨论,一路下来浑身舒畅  

3.13 销毁 

双向链表的销毁,值得注意的是最后哨兵位也要释放掉,因为为了保持用一级指针的连贯性,所以我们选择最后手动在外部将链表指针置为NULL,实现半自动(和free函数的逻辑一致)  

运行结果 

这样我们就完成了对双向链表增删查改等功能的实现 

四、顺序表和双向链表的优缺点分析

我们看到,双向链表的好处是如此巨大的,那是否就代表前面学的顺序表就很low呢? 

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持:O(N)
任意位置插入或者删除元素可能需要搬移元素,效率低O(N)只需修改指针指向
插入动态顺序表,空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁

五、双向链表oj题 

仅仅了解双向链表的知识是不够的,让我们来刷刷题吧! 

141.环形链表(LeetCode)-CSDN博客

142.环形链表 II(LeetCode)-CSDN博客

138.随机链表的复制(LeetCode)-CSDN博客

看到这里了还不给博主扣个:
⛳️ 点赞☀️收藏 ⭐️ 关注

💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖
拜托拜托这个真的很重要!
你们的点赞就是博主更新最大的动力!
有问题可以评论或者私信呢秒回哦。

源代码 

dlist.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

typedef int DLDataType;
typedef struct DListNode
{
	struct DListNode* prev;
	struct DListNode* next;
	DLDataType data;
}DLNode;

//初始化
DLNode* DLInit();
//打印
void DLPrint(DLNode* phead);
//销毁
void DLDestroy(DLNode* phead);

//检测链表是否为空
bool DLEmpty(DLNode* phead);
//尾插
void DLPushBack(DLNode* phead, DLDataType x);
//尾删
void DLPopBack(DLNode* phead);
//头插
void DLPushFront(DLNode* phead, DLDataType x);
//头删
void DLPopFront(DLNode* phead);

//查找
DLNode* DLFind(DLNode* phead, DLDataType x);

//在pos前指定插入
void DLInsert(DLNode* pos, DLDataType x);
//在pos指定删除
void DLErase(DLNode* pos);

dlist.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"dlist.h"

DLNode* BuyDLNode(DLDataType x)
{
	DLNode* newnode = (DLNode*)malloc(sizeof(DLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->prev = NULL;
	newnode->next = NULL;
	return newnode;
}

DLNode* DLInit()
{
	DLNode* phead = BuyDLNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

void DLPrint(DLNode* phead)
{
	assert(phead);
	DLNode* cur = phead;

	printf("Guard");
	while (cur->next != phead)
	{
		cur = cur->next;
		printf("<==>%d", cur->data);
	}
	printf("\n");
}

bool DLEmpty(DLNode* phead)
{
	assert(phead);
	return phead == phead->next;
}

void DLPushBack(DLNode* phead, DLDataType x)
{
	assert(phead);
	DLInsert(phead, x);

	//DLNode* newnode = BuyDLNode(x);
	//DLNode* tail = phead->prev;
	//
	//tail->next = newnode;
	//newnode->prev = tail;
	//phead->prev = newnode;
	//newnode->next = phead;
}

void DLPopBack(DLNode* phead)
{
	assert(phead);
	assert(!DLEmpty(phead));
	DLErase(phead->prev);

	//DLNode* tail = phead->prev;
	//DLNode* tailPrev = tail->prev;
	//
	//free(tail);
	//tailPrev->next = phead;
	//phead->prev = tailPrev;
}

void DLPushFront(DLNode* phead, DLDataType x)
{
	assert(phead);
	DLInsert(phead->next, x);

	//DLNode* newnode = BuyDLNode(x);
	//DLNode* first = phead->next;

	//newnode->next = first;
	//first->prev = newnode;
	//phead->next = newnode;
	//newnode->prev = phead;
}

void DLPopFront(DLNode* phead)
{
	assert(phead);
	assert(!DLEmpty(phead));
	DLErase(phead->next);

	//DLNode* first = phead->next;
	//DLNode* second = first->next;

	//second->prev = phead;
	//phead->next = second;
	//free(first);
}

DLNode* DLFind(DLNode* phead, DLDataType x)
{
	assert(phead);
	DLNode* cur = phead->next;

	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;
	}

	return NULL;
}

void DLInsert(DLNode* pos, DLDataType x)
{
	assert(pos);
	DLNode* newnode = BuyDLNode(x);
	DLNode* prev = pos->prev;

	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

void DLErase(DLNode* pos)
{
	assert(pos);
	DLNode* posPrev = pos->prev;
	DLNode* posNext = pos->next;

	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

void DLDestroy(DLNode* phead)
{
	assert(phead);
	DLNode* cur = phead->next;

	while (cur != phead)
	{
		DLNode* next = cur->next;
		free(cur);
		cur = next;
	}

	free(phead);
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"dlist.h"

TestDList1()
{
	DLNode* plist = DLInit();

	//尾插
	DLPushBack(plist, 1);
	DLPushBack(plist, 2);
	DLPushBack(plist, 3);
	DLPushBack(plist, 4);
	DLPushBack(plist, 5);

	//头插
	DLPushFront(plist, 1);
	DLPushFront(plist, 2);
	DLPushFront(plist, 3);
	DLPushFront(plist, 4);
	DLPushFront(plist, 5);

	//打印
	DLPrint(plist);
}

TestDList2()
{
	DLNode* plist = DLInit();

	//尾插
	DLPushBack(plist, 1);
	DLPushBack(plist, 2);
	DLPushBack(plist, 3);
	DLPushBack(plist, 4);
	DLPushBack(plist, 5);

	//头删
	DLPopFront(plist);
	DLPopFront(plist);
	DLPopFront(plist);

	//打印
	DLPrint(plist);
}

TestDList3()
{
	DLNode* plist = DLInit();

	//头插
	DLPushFront(plist, 1);
	DLPushFront(plist, 2);
	DLPushFront(plist, 3);
	DLPushFront(plist, 4);
	DLPushFront(plist, 5);

	//尾删
	DLPopBack(plist);
	DLPopBack(plist);
	DLPopBack(plist);

	//打印
	DLPrint(plist);
}


TestDList4()
{
	DLNode* plist = DLInit();
	//头插
	DLPushFront(plist, 1);
	DLPushFront(plist, 2);
	DLPushFront(plist, 3);
	DLPushFront(plist, 4);
	DLPushFront(plist, 5);
	//查找与修改
	DLNode* pos = DLFind(plist, 4);
	if (pos != NULL)
	{
		pos->data = 40;
		//在pos前指定插入
		DLInsert(pos, 100);
	}
	//打印
	DLPrint(plist);
}

TestDList5()
{
	DLNode* plist = DLInit();
	//头插
	DLPushFront(plist, 1);
	DLPushFront(plist, 2);
	DLPushFront(plist, 3);
	DLPushFront(plist, 4);
	DLPushFront(plist, 5);
	//查找与修改
	DLNode* pos = DLFind(plist, 4);
	if (pos != NULL)
	{
		pos->data = 40;
		//在pos指定删除
		DLErase(pos);
	}
	//打印
	DLPrint(plist);
}

TestDList6()
{
	DLNode* plist = DLInit();
	//尾插
	DLPushBack(plist, 1);
	DLPushBack(plist, 2);
	//头插
	DLPushFront(plist, 1);
	DLPushFront(plist, 2);
	//打印
	DLPrint(plist);
	//销毁
	DLDestroy(plist);
	plist = NULL;
}

int main()
{
	TestDList6();
	return 0;
}

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

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

相关文章

Python实现求解上个工作日逻辑

目录 一、需求描述二、代码实现三、测试结果 一、需求描述 因工作需要&#xff0c;现需获取任意一个日期的上个工作日&#xff0c;要求考虑法定假日及周末。 例如&#xff1a;2024年2月10日&#xff08;春节&#xff09;的上一个工作日为2024年2月9日&#xff0c;2024年2月17…

【入门篇】1.7 Redis 之 codis 入门介绍

文章目录 1. 简介2. Codis的安装与配置下载编译源码安装1. 安装 Go 运行环境2. 设置编译环境3. 下载 Codis 源代码4. 编译 Codis 源代码 Docker 部署 3. Codis的架构Codis的架构图和组件Codis的工作流程 4. Codis的核心特性自动数据分片数据迁移高可用性全面支持Redis命令分布式…

java代码审计(入门级)—基础漏洞合集

目录 &#xff08;一&#xff09;前言 &#xff08;二&#xff09;经典漏洞的代码审计 1、SQL注入 漏洞原理&#xff1a; 连接数据库的方式&#xff1a; 代码审计 2、XXE&#xff08;XML外部实体注入&#xff09; 漏洞原理 代码审计&#xff1a; 3、xss 漏洞原理 X…

LeetCode之二叉树

发现更多计算机知识&#xff0c;欢迎访问Cr不是铬的个人网站 最近数据结构学到二叉树&#xff0c;就刷了刷力扣&#xff0c;写这篇文章也是辅助记忆。 103二叉树锯齿形遍历 要解出本道题&#xff0c;首先要会层次遍历。层次遍历我们都知道用一个队列去实现就行。但是力扣这里…

【软件安装】Centos系统中安装docker容器(华为云HECS云耀服务器)

这篇文章&#xff0c;主要介绍Centos系统中安装docker容器&#xff08;华为云HECS云耀服务器&#xff09;。 目录 一、安装docker 1.1、卸载旧版本docker 1.2、更新repo镜像 1.3、安装依赖包 1.4、添加docker-ce镜像 1.5、安装docker-ce 1.6、查看docker安装版本 1.7、…

MATLAB 机械臂逆运动学进行轨迹控制建模

系列文章目录 文章目录 系列文章目录前言一、模型概览1.1 Target Pose Generation 目标姿势生成1.2 Inverse Kinematics 逆运动学1.3 Manipulator Dynamics 机械手动力学1.4 Pose Measurement 姿势测量 二、机械手定义三、生成航点四、模型设置五、模拟机械手运动六、将结果可视…

振南技术干货集:比萨斜塔要倒了,倾斜传感器快来!(2)

注解目录 1、倾斜传感器的那些基础干货 1.1 典型应用场景 &#xff08;危楼、边坡、古建筑都是对倾斜敏感的。&#xff09; 1.2 倾斜传感器的原理 1.2.1 滚珠式倾斜开关 1.2.2 加速度式倾斜传感器 1)直接输出倾角 2)加速度计算倾角 3)倾角精度的提高 &#xff08;如果…

2023年亚太杯数学建模思路 - 复盘:人力资源安排的最优化模型

文章目录 0 赛题思路1 描述2 问题概括3 建模过程3.1 边界说明3.2 符号约定3.3 分析3.4 模型建立3.5 模型求解 4 模型评价与推广5 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 描述 …

【LeetCode】每日一题 2023_11_16 最长奇偶子数组(枚举,模拟)

文章目录 刷题前唠嗑K 个元素的最大和题目描述代码与解题思路 结语 刷题前唠嗑 LeetCode? 启动&#xff01;&#xff01;&#xff01; 今天早上概率论期中&#xff0c;被爆杀完之后&#xff0c;下午数电&#xff0c;今天很疲惫很疲惫&#xff0c;一直拖到了现在&#xff0c;终…

【论文阅读】A Survey on Video Diffusion Models

视频扩散模型&#xff08;Video Diffusion Model&#xff09;最新综述GitHub 论文汇总-A Survey on Video Diffusion Models。 paper&#xff1a;[2310.10647] A Survey on Video Diffusion Models (arxiv.org) 0. Abstract 本文介绍了AIGC时代视频扩散模型的全面回顾。简要介…

目标检测—YOLO系列(一)(YOLOv1/2/v3/4/5/x/6/7/8)

目标检测概述 什么是目标检测&#xff1f; 滑动窗口&#xff08;Sliding Window&#xff09; 滑动窗口的效率问题和改进 滑动窗口的效率问题&#xff1a;计算成本很大 改进思路 1&#xff1a;使用启发式算法替换暴力遍历 例如 R-CNN&#xff0c;Fast R-CNN 中使用 Selectiv…

docker 部署日志平台出错汇总

第一次运行elasticsearch:8.11.1镜像&#xff0c;报错如下&#xff1a; [rootmaster ~]# docker run --name es03 --net elastic -p 9200:9200 -it -m 1GB docker.elastic.co/elasticsearch/elasticsearch:8.11.1 docker: Error response from daemon: driver failed programmi…

网络编程TCP/UDP通信

1 网络通信概述 1.1 IP 和端口 所有的数据传输&#xff0c;都有三个要素 &#xff1a;源、目的、长度。 怎么表示源或者目的呢&#xff1f;请看图 所以&#xff0c;在网络传输中需要使用“IP 和端口”来表示源或目的。 1.2 网络传输中的 2 个对象&#xff1a;server 和 cl…

面向配电网韧性提升的移动储能预布局与动态调度策略(matlab代码)

欢迎关注威♥“电击小子程高兴的MATLAB小屋”获取更多资料 该程序复现《面向配电网韧性提升的移动储能预布局与动态调度策略》&#xff0c;具体摘要内容见下图&#xff0c;程序主要分为两大模块&#xff0c;第一部分是灾前预防代码&#xff0c;该部分采用两阶段优化算法&#…

YOLOv8-Seg改进策略:全新的聚焦式线性注意力模块Focused Linear Attention | ICCV2023

🚀🚀🚀本文改进:深入分析了现有线性注意力方法的缺陷,并提出了一个全新的聚焦的线性注意力模块(Focused Linear Attention),同时具有高效性和很强的模型表达能力。 🚀🚀🚀YOLOv8-seg创新专栏:http://t.csdnimg.cn/KLSdv 学姐带你学习YOLOv8,从入门到创新,…

写论文技巧

目录 chatgpt数学公式英文润色中英互译如何搜和下载论⽂1 搜论⽂3 下载论⽂chatgpt 数学公式 英文润色

17. Series.dt.month-提取日期数据中的月份信息

【目录】 文章目录 17. Series.dt.month-提取日期数据中的月份信息1. 知识回顾-创建一个Series对象2. 知识回顾-pd.to_datetime()将数据转换为pandas中的日期时间格式3. 实例化类相关知识4. Series.dt.month是什么&#xff1f;5. 如何使用Series.dt.month&#xff1f;6. Series…

jQuery 【关于jQuery 、 jQuery简介、基础选择器、属性选择器、jQuery扩展】(一)-全面详解(学习总结---从入门到深化)

目录 关于jQuery jQuery简介 选择器之基础选择器(一) 选择器之基础选择器(二) 选择器之属性选择器(一) 选择器之属性选择器(二) 选择器之jQuery扩展(一) 选择器之jQuery扩展(二) 关于jQuery 现在是否还需要学习jQuery&#xff0c;毫无疑问到目前为止&#xff0c;我们仍然…

【Synopsys Bug记录】Synopsys工具显示license过期

首先查找网络配置&#xff0c;打开终端&#xff0c;输入ifconfig&#xff0c;看是否有ens33 若没有ens33&#xff0c;则说明linux的网卡因为某些原因未启用&#xff0c;我们需要更改ifcfg-ens33文件&#xff1b; 输入指令 cd ./etc/sysconfig/network-scripts sudo vim ifcfg-…

关于 内部类 你了解多少?(详解!!)

目录 1. 什么是内部类&#xff1f; 2. 内部类的分类 3. 内部类 3.1 实例内部类 3.2 静态内部类 4. 局部内部类 5. 匿名内部类 6.对象的打印 “不积跬步无以至千里&#xff0c;不积小流无以成江海。”每天坚持学习&#xff0c;哪怕是一点点&#xff01;&#xff01;&a…