数据结构(三)——双向链表的介绍以及实现
前言
前面两期数据结构的文章我们介绍了顺序表和单向链表,那么本篇博文我们将来了解双向链表,作为最好用的一种链表,双向链表有什么特殊之处呢,接下来就让我们一起了解一下吧。
下面是前两篇数据结构的文章:
数据结构(一)——顺序表的介绍
数据结构(二)——链表的介绍以及单链表的实现
双向链表的概念
什么是双向链表呢?实际上,双向链表全称是带头双向循环链表,是一种最复杂但最好用的一种链表形式,它不同于单项链表,它拥有一个头节点,正是由于头节点的存在,弥补了单项链表尾插的不便,同时它拥有两个指针,分别指向前驱和后继,从而方便链表进行数据的挪动等操作。
双向链表的实现
1.定义
目录
前言
双向链表的概念
双向链表的实现
1.定义
2.双向链表接口定义
1.初始化
2.销毁
4.打印
5.尾插/尾删
6.头插/头删
7.任意位置插入/删除
8.查找
小结
typedef int LTDataType;
typedef struct LTNode
{struct LTNode* next;struct LTNode* prev;LTDataType x;
}LTNode;
双向链表的定义如上所示,我们发现,在这个结构体内,我们定义了两个结构体指针,分别指向结构体变量(双向链表)的前驱和后继节点,后续我们将通过这个结构体指针完成链表的头插,尾插等一系列操作。
2.双向链表接口定义
接下来我们将定义一系列链表的接口,通过这些函数,我们将完成链表的初始化、销毁、头插、尾插、头删、尾删、打印等一系列操作。
//链表的初始化
void LTInit(LTNode** pphead);
LTNode* LTInit();
//链表的销毁
void LTDestroy(LTNode* phead);
//链表的打印
void LTPrint(LTNode* phead);//链表的尾插
void LTPushBack(LTNode* phead,LTDataType x);
//链表的头插
void LTPushFront(LTNode* phead, LTDataType x);//链表的尾删
void LTPopBack(LTNode* phead);
//链表的头删
void LTPopFront(LTNode* phead);//在指定位置之后插入
void LTInsert(LTNode* pos, LTNode* phead);
//删除指定位置的节点
void LTErase(LTNode* pos);//查找一个值返回相应节点
LTNode* LTFind(LTNode* phead, LTDataType x);
从上述节点的定义我们发现,不同于单向链表,我们传的大多是一级指针,这是为什么呢?原因在于,双向链表多了一个哨兵位,当链表中只有哨兵位时我们称之为空链表,即哨兵位是不能删除的,而我们大多数操作是不会改变哨兵位的,所以只需要传一级指针。
1.初始化
我们看到,我们给出了两种链表的初始化方式一种是传二级指针,一种则是通过返回值接收,下面我们将实现代码如下:
void LTInit(LTNode** pphead)
{*pphead = (LTDataType*)malloc(sizeof(LTNode));if (*pphead == NULL){perror("malloc fail!");exit(1);}(*pphead)->a = -1;(*pphead)->next = (*pphead)->prev = *pphead;
}
首先是二级指针初始化,我们看到,对二级指针解引用我们可以拿到这个地址,然后malloc一块空间,最后让前驱节点和后继节点都指向自己,这样我们就完成了链表的初始化。但是细心观察我们发现,这一段代码的逻辑是先开一块新的节点,然后再完成初始化,后续我们对链表的插入修改还要用到这一段逻辑,因此我们可以把这个逻辑写成申请节点的函数,这样既减少了代码量,还提高了可读性,所以就有了第二种初始化方式,代码如下:
LTNode* LTBuyNode(LTDataType x)
{//申请一个新的节点LTNode* newnode = (LTDataType*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->a = x;//让新节点的next prev指针指向自己newnode->next = newnode->prev = newnode;return newnode;
}
LTNode* LTInit()
{LTNode* phead = LTBuyNode(-1);return phead;
}
2.销毁
由于链表的每个节点不是连续的,所以我们需要循环销毁每一个节点,有了这个逻辑,我们就可以写下如下代码:
void LTDestroy(LTNode* phead)
{assert(phead);//pcur指向第一个节点LTNode* pcur = phead->next;while (pcur != phead){//记录下一个节点LTNode* next = pcur->next;//销毁该节点free(pcur);//让pcur指向下一个节点pcur = next;}//销毁哨兵位头节点free(phead);phead = NULL;
}
4.打印
与链表销毁类似,打印每个节点的值都是先找到该节点,然后再将该节点的值打印出来,因此我们可以写下如下代码:
void LTPrint(LTNode* phead)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->a);pcur = pcur->next;}printf("\n");
}
5.尾插/尾删
链表插入删除的关键是改变节点的指针,对于尾插而言,我们实际上是将该节点插入哨兵位的前面;对于尾删而言,我们实际上是删除的是哨兵位的前驱节点。因此改变指针指向实际上就是改变哨兵位前驱和后继节点的指向。代码如下:
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->next = phead;newnode->prev = phead->prev;phead->prev->next = newnode;phead->prev = newnode;
}
//尾删
void LTPopBack(LTNode* phead)
{assert(phead);assert(phead->next != phead);LTNode* del = phead->next;LTNode* prve = phead->next->prev;phead->prev = prve;prve->next=phead;free(del);del = NULL;
}
6.头插/头删
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->next = phead->next;newnode->prev = phead;phead->next = newnode;phead->next->prev = newnode;
}
//头删
void LTPopFront(LTNode* phead)
{assert(phead);assert(phead->next != phead);LTNode* prve = phead->next;LTNode* del = phead->next->next;phead->next = del;del->prev = phead;free(prve);prve = NULL;
}
这里有个小技巧,在我们插入时,我们可以先让新节点的前驱和后继节点指向相应位置,改变其他指针的指向
7.任意位置插入/删除
我们在有了前面插入删除的逻辑之后,我们可以用相同的逻辑如法炮制,就可以很快写出任意位置插入删除的代码:
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}
void LTErase(LTNode* pos)
{assert(pos);pos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);pos = NULL;
}
8.查找
查找的逻辑十分简单,通过遍历链表,找到相应元素,然后返回当前节点,如果没有找到,就返回空,代码如下:
LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){if (pcur->a == x){return pcur;}}return NULL;
}
小结
冰冻三尺非一日之寒,在之后的日子里我会持续更新与数据结构相关的内容,如果喜欢的话希望能够点赞关注加转发,您的支持就是对我最大的鼓励,同时也希望在学习路上的你能够坚持下去,半山腰很挤,我想去山顶看看。
相关文章:
数据结构(三)——双向链表的介绍以及实现
前言 前面两期数据结构的文章我们介绍了顺序表和单向链表,那么本篇博文我们将来了解双向链表,作为最好用的一种链表,双向链表有什么特殊之处呢,接下来就让我们一起了解一下吧。 下面是前两篇数据结构的文章: 数据结…...

Webpack开发模式及处理样式资源
一、开发模式介绍 开发模式顾名思义就是我们开发代码时使用的模式。 这个模式下我们主要做两件事: 编译代码,使浏览器能识别运行 开发时我们有样式资源、字体图标、图片资源、html 资源等,webpack 默认都不能处理这些资源,所以我…...
leetcode--设计链表
707.设计链表 你可以选择使用单链表或者双链表,设计并实现自己的链表。 单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。 如果是双向链表,则还需要属性 prev 以指示链表中的…...
【MySQL】:数据库操作
MySQL 数据库基础理论 2.1 数据库系统概述 介绍数据库系统的基本概念、发展历程、分类及 MySQL 在其中的地位与特点。 2.2 MySQL 数据库体系结构 解析 MySQL 的整体架构,包括服务器层与存储引擎层的功能与交互机制,重点探讨 InnoDB、MyISAM 等存…...
刷蓝桥杯历年考题(更新至15届~)
第十五届 CA组省赛 AcWing5980.训练士兵 方法一:树状数组:O(nlogn) self-complete /*先枚举组团,后分析每个士兵,有一个特点,组团费用是固定的,那当然是让所有士兵一块训练,训练完的士兵也不会有损失当还…...

AI与BI的火花:大语言模型如何重塑商业智能的未来
大家好,我是独孤风。 在当今这个数据驱动的时代,企业对于信息的需求如同对于氧气的需求一般至关重要。商业智能(BI)作为企业获取、分析和呈现数据的关键工具,正在经历一场深刻的变革,而这一变革的催化剂正是…...
Qt 详解QtNFC 读写模式
文章目录 Qt NFC 读写模式详解1. NFC 读写模式简介1.1 什么是 NFC 读写模式?主要功能: 1.2 常见应用场景 2. Qt NFC 读写模式原理3. 配置 QtNFC 模块4. NFC 读写操作实现4.1 NFC 标签读取代码示例功能解析 4.2 NFC 标签写入代码示例功能解析 5. 使用注意…...
增删改查文档
列表 : 列表包含 : 模糊查找 分页 列表jsp页面 : 一 :导入外部文件 (举例 : 用户点进来就可以看到菜单,这是预加载属于,使用文档就绪函数实现) 二 : body 上 ① : 文档就绪函数 ${ function() //获取条件查询的字段 //组装对象 //调用文档就绪函数 } ② : 封装ajax方…...
C语言蓝桥杯2023年省赛真题
文章目录 持续更新中...第一题题目描述输入格式输出格式样例输出提示 2 第二题题目描述 第三题题目描述输入格式输出格式样例输入样例输出 第四题题目描述输入格式输出格式样例输入样例输出提示 第四题题目描述输入格式输出格式样例输入样例输出提示 第五题题目描述输入格式输出…...
Python迭代器-大数据量的处理
一 生成器的实际使用(大量数据的导出) #分批导出数据然后分批写入excel import pandas as pd import openpyxl from openpyxl.utils.dataframe import dataframe_to_rowsdef execute_query(query):# 假设这是执行 SQL 查询的函数# 返回查询结果passdef …...
自动化包括态交互与感交互,而智能化包括势交互与知交互
“自动化包括态交互与感交互,而智能化包括势交互与知交互”交互框架将交互过程划分为不同类型,有助于更清晰地理解自动化和智能化的本质及其在未来agent应用中的差异与联系。 1. 自动化:态交互与感交互 自动化主要关注的是高效、无差错地执行…...

VideoBooth: Diffusion-based Video Generation with Image Prompts
VideoBooth: Diffusion-based Video Generation with Image Prompts 概括 文章提出了一个视频生成模型VideoBooth,输入一张图片和一个文本提示词,即可输出保持图片中物体且符合文本提示词要求的视频。 方法 粗-细两阶段设计:1)…...

模拟简单的iOT工作流
没有实际接触过iOT的流程,应该实际使用比这个接口返回要复杂,只是演示~希望能参与实际的接口接入,而不是只展示个假数据。 启动RabbitQ 使用的是3.8.5 启动命令 RabbitMQ Service - start RabbitMQ Command Prompt rabbitmqctl start_app …...
C++学习0.2: RAII
引用: 【代码质量】RAII在C编程中的必要性_raii 在c中的重要性-CSDN博客 C RAII典型应用之lock_guard和unique_lock模板_raii lock-CSDN博客 前言: 常用的线程间同步/通信(IPC)方式有锁(互斥锁、读写锁、自旋锁)、…...

k8s,进一步理解Pod
比如,凡是调度、网络、存储,以及安全相关的属性,基本上是Pod 级别的。 这些属性的共同特征是,它们描述的是“机器”这个整体,而不是里面运行的“程序”。比如,配置这个“机器”的网卡(即&#…...

MFC图形函数学习13——在图形界面输出文字
本篇是图形函数学习的最后一篇,相关内容暂告一段落。 在图形界面输出文字,涉及文字字体、大小、颜色、背景、显示等问题,完成这些需要系列函数的支持。下面做简要介绍。 一、输出文本函数 原型:virtual BOOL te…...

【Canvas与雷达】点鼠标可暂停金边蓝屏雷达显示屏
【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>点鼠标可暂停金边蓝屏雷达显示屏 Draft1</title><style typ…...
React第十二节组件之间通讯之发布订阅模式(使用pubsub-js插件)
组件之间通讯常用方案 1、通过props 2、通过context 3、通过发布订阅模式 4、通过Redux 后面会有专栏介绍 1、安装 pubsub-js 插件 yarn add pubsub-js 常用的事件 a、发布事件:传入一个自定义事件名称(name),以及要发布的消息内…...

Vue3安装 运行教程
本文是综合了所有vue安装教程而成 更细化 更简略 希望对各位读者有所帮助! Vue安装 1. Vue-cli脚手架安装 安装vue的方式有很多 我们这里选择npm方式安装vue npm方式 npm方式安装vue,详细介绍见下文。 1.node.js安装和配置 安装npm 需要安装note.js&…...
MySQL:约束constraint
约束就是表中数据的限制条件. 表在设计的时候加入约束的目的是为了保证表中记录的完整性和有效性,如用户表有些列的值(手机号)不能为空,有些列的值(身份证号)不能重复。 主键约束(primary key) PK MySQL主…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...

相关类相关的可视化图像总结
目录 一、散点图 二、气泡图 三、相关图 四、热力图 五、二维密度图 六、多模态二维密度图 七、雷达图 八、桑基图 九、总结 一、散点图 特点 通过点的位置展示两个连续变量之间的关系,可直观判断线性相关、非线性相关或无相关关系,点的分布密…...