数据结构与算法——双向链表
双向链表
- 定义
- 链表分类
- 双向链表:带头双向循环链表
- 初始化
- 打印
- 尾插
- 头插
- 尾删
- 头删
- 查找
- 在pos(指定位置)之后插入结点
- 在pos(指定位置)之前插入结点
- 删除pos(指定位置)的结点
- 销毁
- 顺序表与链表的分析
定义
链表分类
单向和双向
带头和不带头
带头是指存在一个头结点(哨兵位),但是不存储任何有效数据,用来占位子
注意:单链表是不带头的,方便理解,有时候称为第一个结点为头结点,实际上是没有头结点的
循环和不循环
双向链表:带头双向循环链表
组成:保存的数据 + 指向下一个结点的指针 + 指向上一个节点的指针
单链表为空时,指向第一个节点的指针为空,双链表为空时,链表中只有一个头结点!
初始化
法一:定义一个双向链表的结构体,然后将指向双向链表的指针plist作为形参开始初始化然后返回
法二:通过函数的返回值来返回
函数的声明
//结构的定义
typedef int LTDataType;
typedef struct ListNode {LTDataType data;struct ListNode* next;struct ListNode* prev;
}LTNode;//初始化(法一)
void LTInit(LTNode** pphead);
//法二:
//LTNode* LTInit();
函数的实现
#include"List.h"//申请空间
LTNode* LTBuyNode(LTDataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = x;newnode->next = newnode->prev = newnode;return newnode;
}//初始化(法一)
void LTInit(LTNode** pphead)
{assert(pphead);*pphead = LTBuyNode(-1);
}/*法二
LTNode* LTInit()
{LTNode* phead = LTBuyNode(-1);return phead;//返回双向链表的头结点
}*/
函数的测试
#include"List.h"void test01()
{ //法一:LTNode* plist = NULL;//单链表为空时,指向第一个节点的指针为空//双链表为空时,链表中只有一个头结点!LTInit(&plist);/*法二:LTNode* plist = LTInit();*/
}int main()
{test01();return 0;
}
法一:相当于你提着一个空的油桶(一个形参)去买油,然后给你装满油;
法二:相当于你空手去买油,然后连油带桶给你(推荐这种)。
打印
单链表的打印是从第一个结点开始往后遍历,且第一个结点不能为空while(pcur),但是双链表的第一个结点(哨兵位)就不储存数据,所以当还是用相同的办法会陷入死循环,如何解决?
将哨兵位的下一个结点定为pcur,向后遍历,停止的标志为pcur = phead
//打印
void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead) //将哨兵位的下一个结点定为pcur,向后遍历,停止的标志为pcur = phead{printf("%d -> ", pcur->data);pcur = pcur->next;}printf("\n");
}
尾插
双向链表中尾插,头结点不会发生改变(哨兵位),所以传一级指针。
phead指向链表的第一个结点,通过phead->prev找到尾结点,然后申请一个新结点(newnode),当尾插进来,受到影响的指针有“尾结点的next指针不再指向头结点,而是指向newnode;头结点的prev指针不再指向尾结点,而是指向newnode”,最后newnode的next指针指向头结点。
但是修改的顺序是?先修改对原链表没有影响的结点,因为先修改头结点的prev指针指向newnode后你就找不到原来的尾结点了;通常一般先改变newnodeprev和next指针,最后改头结点受到影响的。
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->prev = phead->prev;newnode->next = phead;phead->prev->next= newnode;phead->prev = newnode;
}
头插
newnode放在哨兵位的后面,如果放在哨兵位的前面则相当于尾插。
phead指向链表的第一个结点,通过phead->next找到哨兵位下一个结点,然后申请一个新结点(newnode),当头插进来,受到影响的指针有“头结点的next指针不再指向哨兵位下一个结点,而是指向newnode;哨兵位下一个结点的prev指针不再指向头结点,而是指向newnode”,最后newnode的next指针指向头结点。
但是修改的顺序是?先修改对原链表没有影响的结点,因为先修改头结点的next指针指向newnode后你就找不到原来哨兵位下一个结点了;通常一般先改变newnodeprev和next指针,最后改头结点受到影响的。
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;
}
尾删
删除phead->prev指针,思考能不能直接释放掉?
如果直接释放掉,phead->prev和phead->prev->prev->next均会变成野指针,所以要先将phead->prev->prev->next指向头结点,然后将头结点的prev指针指向phead->prev->prev->next。删除之前要将此结点存起来del = phead->prev,执行完上述操作后释放掉,并且置位空。
//判断链表是否为空:只有一个头结点,记得在头文件声明
bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
} // 返回true为空,反回flase不为空//尾删
void LTPopBack(LTNode* phead)
{assert(!LTEmpty(phead));LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}
头删
删除phead->next指针,先将del->next->prev指向头结点,然后将头结点的next指针指向del->next。删除之前要将此结点存起来del = phead->next,执行完上述操作后释放掉,并且置位空。
//判断链表是否为空:只有一个头结点
bool LTEmpty(LTNode* phead)
{assert(phead);return phead->next == phead;
} // 返回true为空,反回flase不为空//头删
void LTPopFront(LTNode* phead)
{assert(!LTEmpty(phead));LTNode* del = phead->next;del->next->prev = phead;phead->next = del->next;free(del);del = NULL;}
以上时间复杂度全是O(1),不需要遍历
查找
定义一个pcur为phead的next结点,然后进行遍历查找,找到了就返回查找的值,没有找到就返回空,循环的条件是pcur不走到头结点。
//查找,记得添加到头文件
LTNode* LTFind(LTNode* phead, LTDataType x)
{LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}
在pos(指定位置)之后插入结点
先修改对原链表没有影响的结点
受到影响的结点:pos,pos->next这两个结点
还是先改newnode指针的指向,然后改pos->next指向的结点,最后改pos结点
//在pos位置后插入结点
void LTInsertBack(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}
在pos(指定位置)之前插入结点
先修改对原链表没有影响的结点
受到影响的结点:pos,pos->prev这两个结点
还是先改newnode指针的指向,然后改pos->prev指向的结点,最后改pos结点
//在pos位置前插入结点
void LTInsertFront(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos;newnode->prev = pos->prev;pos->prev->next = newnode;pos->prev = newnode;
}
删除pos(指定位置)的结点
//删除pos位置的结点
void LTErase(LTNode* pos)
{assert(pos);pos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);pos = NULL;
}
销毁
定义一个pcur指向头结点的下一个结点的地址,当pcur不等于头结点(哨兵位)时,进入循环,将pcur的下一个结点先保存下来,然后销毁pcur,然后让pcur走到刚刚保存的下一个结点,就这样进行遍历,最后销毁完全部后在手动销毁头结点并且置位空。
//链表的销毁,因为形参会影响实参,所以传二级指针
void LTDestroy(LTNode** pphead)
{LTNode* pcur = (*pphead)->next;while (pcur != *pphead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(*pphead);*pphead = NULL;
}
void test01()
{LTDestroy(&plist);
}
但是这个定义的函数违背了接口一致性?因为其他的所有函数都是传的一级指针,此处传的二级,如何解决?
void LTDestroy(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}free(phead);phead = NULL;
}
因为这样不能改变形参的值,需要手动置空
void test01()
{LTDestroy(plist);plist = NULL;
}
顺序表与链表的分析
对比维度 | 顺序表 | 单链表 | 双向链表 |
---|---|---|---|
存储结构 | 物理连续(数组) | 节点 + 单向指针 | 节点 + 前驱/后继双指针 |
随机访问效率 | O(1) | O(n) | O(n) |
头插/头删效率 | O(n)(需移动元素) | O(1) | O(1) |
尾插/尾删效率 | O(1)(若预留空间) | O(n)(需遍历) | O(1)(直接访问尾节点) |
任意位置插入删除 | O(n) | O(n)(需找前驱节点) | O(1)(已知位置时) |
空间开销 | 仅数据 | 数据 + 1个指针/节点 | 数据 + 2个指针/节点 |
内存灵活性 | 需预分配,可能浪费 | 动态分配,无浪费 | 动态分配,无浪费 |
逆向遍历 | 支持(但效率同正向) | 不支持 | 支持(O(n)) |
适用场景 | 高频访问、数据量稳定 | 频繁头操作、未知数据量 | 需要双向操作(如LRU缓存) |
相关文章:

数据结构与算法——双向链表
双向链表 定义链表分类双向链表:带头双向循环链表 初始化打印尾插头插尾删头删查找在pos(指定位置)之后插入结点在pos(指定位置)之前插入结点删除pos(指定位置)的结点销毁顺序表与链表的分析 定义 链表分类 单向和双向 带头和不带头 带头是指存在一个头结点&…...

MODBUS RTU调试助手使用方法详解
一、软件简介 485调试助手是一款常用的串口通信调试工具,专门用于RS-485总线设备的测试、调试和通信监控。它支持多种串口参数设置,提供数据收发功能,是工业现场调试的必备工具之一。 二、软件安装与启动 1. 系统要求 Windows 7/10/11操作…...

自由学习记录(60)
Lecture 16 Ray Tracing 4_哔哩哔哩_bilibili 老师说的“高频采样”问题是什么? 现在考虑一个特殊情况: ❗ 一个像素内,图像信号变化很剧烈(高频): 比如: 细网格纹理 马赛克背景 很高频的…...

现代计算机图形学Games101入门笔记(三)
三维变换 具体形式缩放,平移 特殊点旋转。这里涉及到坐标系,先统一定义右手坐标系,根据叉乘和右手螺旋判定方向。这里还能法线Ry Sina 正负与其他两个旋转不一样。这里可以用右手螺旋,x叉乘z,发现大拇指朝下࿰…...

WeakAuras Lua Script <BiaoGe>
WeakAuras Lua Script <BiaoGe> 表格拍卖插件WA字符串 表格字符串代码: !WA:2!S3xA3XXXrcoE2VH9l7ZFy)C969PvDpSrRgaeuhljFlUiiSWbxaqXDx(4RDd0vtulB0fMUQMhwMZJsAO5HenLnf1LPSUT4iBrjRzSepL(pS)e2bDdWp5)cBEvzLhrMvvnAkj7zWJeO7mJ8kYiJmYiImYF0b(XR)JR9JRD…...
计算机视觉与深度学习 | LSTM应用合集
LSTM **一、时间序列预测****二、自然语言处理(NLP)****三、语音识别与合成****四、视频分析与行为识别****五、异常检测****六、医疗健康****七、推荐系统****八、金融风控****九、机器人控制****十、其他创新应用****十一、LSTM的局限性及替代方案****十二、总结**长短期记…...
在Verilog中,逻辑右移(Logical Right Shift)和算术右移(Arithmetic Right Shift)的区别
在Verilog中,逻辑右移(Logical Right Shift)和算术右移(Arithmetic Right Shift)的核心区别在于左侧空位的填充方式,具体如下: 逻辑右移(>>) 操作符:&g…...
Go语言 GORM框架 使用指南
在 Go 语言社区中,数据库交互一直是开发者们关注的重点领域,不同开发者基于自身的需求和偏好,形成了两种主要的技术选型流派。一部分开发者钟情于像sqlx这类简洁的库,尽管其功能并非一应俱全,但它赋予开发者对 SQL 语句…...
STM32控制电机
初始化时钟:在 STM32 的程序中,初始化系统时钟,一般会使用 RCC(Reset and Clock Control)相关函数来配置时钟。例如,对于 STM32F103 系列,可能会使用 RCC_APB2PeriphClockCmd 函数来使能 GPIO 和…...
力扣刷题(第二十九天)
灵感来源 - 保持更新,努力学习 - python脚本学习 验证回文串 解题思路 验证回文串的核心在于判断一个字符串是否从前向后和从后向前读都是一样的。不过,题目通常会有两个主要限制条件: 忽略大小写:比如 "A man" …...

chrome 浏览器插件 myTools, 日常小工具。
1. 起因, 目的: 比如,chatgpt, google, 打开网页,就能直接输入文字,然后 grok 就不行,必须用鼠标点一下,才能输入文字。 对我而言,是个痛点!写个插件,自动点…...
Leaflet使用SVG创建动态Legend
接前一篇文章,前一篇文章我们使用 SVG 创建了带有动态文字的图标,今天再看看怎样在地图上根据动态图标生成相关的legend,当然这里也还是使用了 SVG 来生成相关颜色的 legend。 看下面的代码,生成了一个 svg 节点,其中…...

智慧校园(含实验室)智能化专项汇报方案
该方案聚焦智慧校园(含实验室)智能化建设,针对传统实验室在运营监管、环境监测、安全管控、排课考勤等方面的问题,依据《智慧校园总体框架》等标准,设计数字孪生平台、实验室综合管理平台、消安电一体化平台三大核心平台,涵盖通信、安防、建筑设备管理等设施,涉及 395 个…...

第三十四节:特征检测与描述-SIFT/SURF 特征 (专利算法)
一、特征检测:计算机视觉的基石 在计算机视觉领域中,特征检测与描述是实现图像理解的核心技术。就像人类通过识别物体边缘、角点等特征来认知世界,算法通过检测图像中的关键特征点来实现: 图像匹配与拼接 物体识别与跟踪 三维重建 运动分析 其中,SIFT(Scale-Invariant F…...
ORACLE数据库实例报错ORA-00470: LGWR process terminated with error宕机问题分析报告
服务概述 10月21号03:22分,BOSS数据库实例发生异常宕机;工程师及时响应此问题并对此故障原因进行分析及相关建议,详细的故障情况及相关日志、TRACE文件的分析及总结、建议,请参阅本文档。 hzboss数据库实例宕机分析 4.1 数据库层面日志的分…...

【前端优化】vue2 webpack4项目升级webpack5,大大提升运行速度
记录一下过程 手里有个老项目,vue2webpack4 项目很大,每次运行、运行都要将近10分钟 现在又要往里面写很多东西,再不优化,开发着会更难受,所以决定先将它升级至webpack5 最初失败的尝试 直接在项目里安装了webpack5 但…...

Nginx应用场景详解与配置指南
1. 什么是Nginx? Nginx(发音为"engine-x")是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP代理服务器。它以高性能、稳定性、丰富的功能集、简单的配置和低资源消耗而闻名。 2. Nginx的主要应用场景 2.1 …...

vue2 切换主题色以及单页面好使方法
今天要新增一个页面要根据不同公司切换不同页面主题色,一点一点来,怎么快速更改 el-pagination 分页组件主题色。 <el-pagination :page-size"pageSize" :pager-count"pageCount"layout"sizes, prev, pager, next, jumper,…...
React学习———CSS Modules(样式模块化)
CSS Modules CSS Modules(样式模块化)是一种用于模块化和局部作用域化CSS样式的技术,让CSS只在当前组件内生效,避免全局样式冲突的技术方案 工作原理 文件命名:通常以.module.css、.module.less、.module.scss等结尾…...
MCP 与 Cloudflare 的结合:网络安全的新高度
MCP 与 Cloudflare 的结合:网络安全的新高度 在数字化时代,网络安全已经不只是某些行业的“专属问题”,而是所有企业、个人都必须面对的核心挑战。从 DDoS 攻击、数据泄露,到身份盗用,每一种网络威胁都可能带来巨大的损失。而这时候,微软 MCP(Microsoft Cloud Platform…...
JavaScript入门【1】概述
1.JavaScript是什么? <font style"color:rgb(38,38,38);">Javascript (简称“JS”)是⼀种直译式脚本语⾔,⼀段脚本其实就是⼀系列指令,计算机通过这些指令来达成⽬标。它⼜是⼀种动态类型的编程语⾔。JS⽤来在⽹…...
PyQt5 的使用
PyQt5 是 Python 里基于 Qt 框架的 GUI 开发工具,能做桌面应用,跨平台(Windows/macOS/Linux 都能用)。你可能想知道:怎么开始用?有哪些核心组件?怎么写界面逻辑?别急,咱们…...

JavaScript【6】事件
1.概述: 在 JavaScript 中,事件(Event)是浏览器或 DOM(文档对象模型)与 JavaScript 代码之间交互的一种机制。它代表了在浏览器环境中发生的特定行为或者动作,比如用户点击鼠标、敲击键盘、页面…...

STM32F10xx 参考手册
6. 什么是寄存器 本章参考资料:《STM32F10xx 参考手册》、《STM32F10xx数据手册》、 学习本章时,配合《STM32F10xx 参考手册》“存储器和总线架构”及“通用I/O(GPIO)”章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。…...
使用Docker部署Nacos
sudo systemctl start docker sudo systemctl enable docker docker --version 步骤 2: 拉取 Nacos Docker 镜像 拉取 Nacos 镜像: 你可以从 Docker Hub 上拉取官方的 Nacos 镜像,使用以下命令: docker pull nacos/nacos-server 这会从 …...
深度学习中ONNX格式的模型文件
一、模型部署的核心步骤 模型部署的完整流程通常分为以下阶段,用 “跨国旅行” 类比: 步骤类比解释技术细节1. 训练模型学会一门语言(如中文)用 PyTorch/TensorFlow 训练模型2. 导出为 ONNX翻译成国际通用语言(如英语…...

TIFS2024 | CRFA | 基于关键区域特征攻击提升对抗样本迁移性
Improving Transferability of Adversarial Samples via Critical Region-Oriented Feature-Level Attack 摘要-Abstract引言-Introduction相关工作-Related Work提出的方法-Proposed Method问题分析-Problem Analysis扰动注意力感知加权-Perturbation Attention-Aware Weighti…...

Redis 发布订阅模式深度解析:原理、应用与实践
在现代分布式系统架构中,实时消息传递机制扮演着至关重要的角色。Redis 作为一款高性能的内存数据库,其内置的发布订阅(Pub/Sub)功能提供了一种轻量级、高效的消息通信方案。本文将全面剖析 Redis 发布订阅模式,从其基本概念、工作原理到实际…...
环形缓冲区 ring buffer 概述
环形缓冲区 ring buffer 概述 1. 简介 环形缓冲区(ring buffer),是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。也称作环形缓冲区(circular buffer),环形队列&…...

飞帆控件 post or get it when it has get
我在这里分享两个链接: post_get_it 设计 - 飞帆 有人看出来这个控件是干什么用吗? 控件的配置:...