NzN的数据结构--实现双向链表
上一章中,我们学习了链表中的单链表,那今天我们来学习另一种比较常见的链表--双向链表!!

目录
一、双向链表的结构
二、 双向链表的实现
1. 双向链表的初始化和销毁
2. 双向链表的打印
3. 双向链表的头插/尾插
4. 双向链表的头删/尾删
5. 查找数据是否存在
6. 在指定位置之后插入数据
7. 删除指定位置的数据
8. 判断双向链表是否为空
三、顺序表和双向链表的优缺点分析
一、双向链表的结构

“哨兵位”存在的意义:遍历循环链表避免死循环。
注意:带头链表里的头节点,实际为“哨兵位”,不存储任何有效数据。
typedef int LTDataType;
typedef struct ListNode
{LTDataType data;//存储的数据struct ListNode* prev; //指针保存前一个节点的地址struct ListNode* next; //指针保存下一个节点的地址
}LTNode;
二、 双向链表的实现
我们先在头文件中定义需要实现的相关接口。
//List.h
#include<stdio.h>
#include <stdbool.h>//引用bool类型
#include<stdlib.h>
typedef int LTDataType;
typedef struct ListNode
{LTDataType data;//存储的数据struct ListNode* prev; //指针保存前一个节点的地址struct ListNode* next; //指针保存下一个节点的地址
}LTNode;
//创建节点
LTNode* LTBuyNode(LTDataType x);
//双向链表有哨兵位,插入数据之前链表中必须初始化一个哨兵位
//需要修改哨兵位就要传二级指针
//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);
//查找数据是否存在
LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);
//删除pos位置的数据
void LTErase(LTNode* pos);
//判断链表是否为空
bool LTEmpty(LTNode* phead);
1. 双向链表的初始化和销毁
//双向链表初始化
void LTInit(LTNode** pphead)
{*pphead = (LTNode*)malloc(sizeof(LTNode));if (*pphead == NULL){perror("malloc fail");exit(1);}(*pphead)->data = -1;//给哨兵位一个无效的数据,是多少都可以//带头双向循环链表在刚初始化一个哨兵位时,next和prev都指向自己(*pphead)->next = (*pphead)->prev = *pphead;return phead;
}
这种写法要涉及到二级指针,非常麻烦,那我们尝试简化一下代码。
LTNode* LTInit()
{LTNode*phead= (LTNode*)malloc(sizeof(LTNode));if (phead == NULL){perror("malloc fail");exit(1);}phead->data = -1;phead->next = phead->prev = phead;return phead;
}
实际上,这段代码还可以进行简化。因为双向链表为空时,仍然有一个哨兵位,那我们在初始化时就可以直接申请一个哨兵位。
//将申请节点的功能进行封装
LTNode* LTBuyNode(LTDataType x) {LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL) {perror("malloc");exit(1);}newnode->data = x;newnode->next = newnode->prev = newnode;return newnode;
}
//双向链表初始化
LTNode* LTInit()
{LTNode* phead = LTBuyNode(-1);//申请哨兵位return phead;
}
//双向链表销毁
void LTDestroy(LTNode* phead)
{assert(phead);//遍历链表,把每一个节点都释放LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}//链表中哨兵位也要释放free(phead);phead = NULL;
}
2. 双向链表的打印
//双向链表打印
void LTPrint(LTNode* phead)
{assert(phead);//phead不能为空LTNode* pcur = phead->next;while (pcur != phead){//从第一个节点开始走,走到哨兵位结束printf("%d->", pcur->data);pcur = pcur->next;}printf("\n");
}
3. 双向链表的头插/尾插
void LTPushBack(LTNode* phead, LTDataType x)
{LTNode* newnode = LTBuyNode(x);//ptail->next=phead;//尾节点的next指向哨兵位//phead->prev=ptail//哨兵位的prev指向尾节点//新尾节点的next要指向哨兵位newnode->next = phead;//新尾节点的prev要指向原来的尾节点newnode->prev = phead->prev;//原来尾节点的next指向新的尾节点phead->prev->next = newnode;//哨兵位的prev连接新的尾节点phead->prev = newnode;
}
void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);//新的头节点的next指向原来的头节点newnode->next = phead->next;//新的头节点的prev指向哨兵位newnode->prev = phead;//原来头节点的prev指向新的头节点phead->next->prev = newnode;//哨兵位的next指向新的头节点phead->next = newnode;
}
4. 双向链表的头删/尾删
void LTPopBack(LTNode* phead)
{assert(phead);//链表不能为空(只有一个哨兵位)assert(phead->next != phead);LTNode* del = phead->prev;LTNode* prev = del->prev;//原来尾节点的前一个节点的next指向哨兵位prev->next = phead;//哨兵位的prev变成原来尾节点的前一个节点phead->prev = prev;//释放原来的尾节点free(del);del = NULL;
}
void LTPopFront(LTNode* phead)
{assert(phead);//链表不能为空(只有一个哨兵位)assert(phead->next != phead);LTNode* del = phead->next;LTNode* next = del->next;//原来头节点的后一个节点的prev指向哨兵位next->prev = phead;//哨兵位的next变成原来头节点的后一个节点phead->next = next;//释放原来的尾节点free(del);del = NULL;
}
5. 查找数据是否存在
LTNode* LTFind(LTNode* phead, LTDataType x)
{assert(phead);LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x) {return pcur;}pcur = pcur->next;}return NULL;
}
6. 在指定位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);//新节点的next指向pos后面原来的节点newnode->next = pos->next;//新节点的prev指向pos节点newnode->prev = pos;//pos节点后面原来的节点的prev换成新节点pos->next->prev = newnode;//pos节点的next换成新节点pos->next = newnode;
}
7. 删除指定位置的数据
void LTErase(LTNode* pos)
{assert(pos);//pos前面节点的next指向pos后面的节点pos->prev->next = pos->next;//pos后面节点的prev指向pos前面的节点pos->next->prev = pos->prev;free(pos);pos = NULL;
}
8. 判断双向链表是否为空
bool LTEmpty(LTNode* phead)
{return phead->next == phead;
}
三、顺序表和双向链表的优缺点分析
| 顺序表 | 带头双向循环链表 | |
| 优点 | 下标随机访问(实现二分查找、排序、堆算法等); Cache命中率高(存储空间连续) | 任意位置插入删除数据效率高; 按需申请、释放,不存在空间浪费 |
| 缺点 | 前面部分的插入删除,效率低下; 扩容会有效率损失,还可能会存在空间浪费 | 不支持下标随机访问; Cache命中率低(存储空间不连续) |
相关文章:
NzN的数据结构--实现双向链表
上一章中,我们学习了链表中的单链表,那今天我们来学习另一种比较常见的链表--双向链表!! 目录 一、双向链表的结构 二、 双向链表的实现 1. 双向链表的初始化和销毁 2. 双向链表的打印 3. 双向链表的头插/尾插 4. 双向链表的…...
easyexcel-获取文件资源和导入导出excel
1、获取本地资源文件,根据模板填充数据导出 public void exportExcel(HttpServletResponse httpResponse, RequestBody AssayReportDayRecordQuery query) {AssayReportDayRecordDTO dto this.queryByDate(query);ExcelWriter excelWriter null;ExcelUtil.config…...
Android Monkey自动化测试
monkey一般用于压力测试,用户模拟用户事件 monkey 基本用法 adb shell monkey [参数] [随机事件数]monkey常用命令 -v:用于指定反馈信息级别,总共分三个等级-v -v -vadb shell mokey -v -v -v 100-s:用于指定伪随机数生成器的种…...
C++ //练习 11.20 重写11.1节练习(第376页)的单词计数程序,使用insert代替下标操作。你认为哪个程序更容易编写和阅读?解释原因。
C Primer(第5版) 练习 11.20 练习 11.20 重写11.1节练习(第376页)的单词计数程序,使用insert代替下标操作。你认为哪个程序更容易编写和阅读?解释原因。 环境:Linux Ubuntu(云服务…...
Nginx 安装与实践
目录 一、安装 Nginx1、先安装 Brew2、再安装 Nginx 二、常用的 Nginx 命令三、简单的 Nginx 配置四、查看日志的 Linux 命令1、查看日志的 Linux 命令2、实时查看项目运行时打印的日志 一、安装 Nginx 推荐使用 HomeBrew 来安装 Nginx。 1、先安装 Brew 详见:Home…...
QT 创建线程的几种方法
//qt创建线程的几种方法 //在Qt中,创建线程的主要方法有以下几种: //1.继承QThread类重写run方法 class MyThread : public QThread { Q_OBJECT public: void run() override { // 在这里执行你的代码 } }; // 使用 MyThread *myThread n…...
RocketMQ的简单使用
这里需要创建2.x版本的springboot项目 导入依赖 <dependencies><dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.2.3</version></dependency>&…...
速盾:服务器有cdn 带宽上限建议多少
CDN(内容传输网络)是一种通过分布在全球不同地点的服务器来提供高效内容分发的技术。当用户请求访问某个网站时,CDN会根据用户的地理位置,将内容从离用户最近的服务器上提供给用户,这样可以减少延迟和带宽消耗…...
智慧工地安全+绿色施工方案
塔机监测 塔吊监测可以实现对塔机监测、群塔防碰撞、塔机区域防护和吊钩可视化 1司机身份识别认证:只有司机在监控设备进行刷卡、指纹、人脸、虹膜验证身份后才能进行设备的作业操作。 2运行工况采集与显示:清晰实时显示起重机械设备运行工况,主要显示的内容:起重量、起…...
SQL Server 存储过程:BBS论坛(表结构文档下载及30个存储过程)
基于 Asp.Net 和 SQL Server 实现了一个BBS论坛,论坛功能比较强大,论坛大部分业务逻辑基于存储过程实现,记录一下。 BBS论坛存储过程清单 序号存储过程功能说明1sp_bbs_admin_add添加管理员2sp_bbs_admin_del删除系统管理员3sp_bbs_admin_m…...
03 Python进阶:MySQL - mysql-connector
mysql-connector安装 要在 Python 中使用 MySQL 数据库,你需要安装 MySQL 官方提供的 MySQL Connector/Python。下面是安装 MySQL Connector/Python 的步骤: 首先,确保你已经安装了 Python,如果没有安装,可以在 Python…...
InnoDB 行记录格式(“存储一行行数据的结构“)
1.行格式 1.1 Compact行格式 1.1.1 示意图 1.1.2 准备一下 1)建表 mysql> CREATE TABLE record_format_demo (-> c1 VARCHAR(10),-> c2 VARCHAR(10) NOT NULL,-> c3 CHAR(10),-> c4 VARCHAR(10)-> ) CHARSETascii ROW_FORMATCOM…...
【洛谷】P9236 [蓝桥杯 2023 省 A] 异或和之和
题目链接 P9236 [蓝桥杯 2023 省 A] 异或和之和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路 1. 暴力求解 直接枚举出所有子数组,求每个子数组的异或和,再对所有的异或和求和 枚举所有子数组的时间复杂度为O(N^2)&…...
ThreadLocal加切面实现线程级别的方法缓存
1、实现效果 当一个请求线程多次请求A方法时,只会触发一次A方法的实际调用,会将方法结果缓存起来,避免多次调用。 2、实现过程 1. 需要一个注解ThreadLocalCache,在需要缓存的方法上加上该注解 2. 需要一个切面,借助ThreadLocal,将结果缓存起来,利用环绕通知来实现方法拦截从…...
使用 Flume 将 CSV 数据导入 Kafka:实现实时数据流
使用 Flume 将 CSV 数据导入 Kafka:实现实时数据流 文介绍了如何使用 Apache Flume 将 CSV 格式的数据从本地文件系统导入到 Apache Kafka 中,以实现实时数据流处理。通过 Flume 的配置和操作步骤,我们可以轻松地将数据从 CSV 文件中读取并发…...
对代理模式的理解
目录 一、前言二、案例1 代码2 自定义代理类【静态代理】2.1 一个接口多个实现,到底注入哪个依赖呢?2.1.1 Primary注解2.1.2 Resource注解(指定name属性)2.1.3 Qualifier注解 2.2 面向接口编程2.3 如果没接口咋办呢?2.…...
#QT项目实战(天气预报)
1.IDE:QTCreator 2.实验: 3.记录: (1)调用API的Url a.调用API获取IP whois.pconline.com.cn/ipJson.jsp?iphttp://whois.pconline.com.cn/ipJson.jsp?ip if(window.IPCallBack) {IPCallBack({"ip":&quo…...
数据挖掘|关联分析与Apriori算法详解
数据挖掘|关联分析与Apriori算法 1. 关联分析2. 关联规则相关概念2.1 项目2.2 事务2.3 项目集2.4 频繁项目集2.5 支持度2.6 置信度2.7 提升度2.8 强关联规则2.9 关联规则的分类 3. Apriori算法3.1 Apriori算法的Python实现3.2 基于mlxtend库的Apriori算法的Python实现 1. 关联分…...
ChatGPT Excel 大师
原文:ChatGPT Excel Mastery 译者:飞龙 协议:CC BY-NC-SA 4.0 序言 欢迎来到 Excel 掌握的变革之旅,在这里,尖端技术和永恒专业知识在“ChatGPT Excel 掌握:释放专家技巧和窍门的力量”中融合。在当今快节…...
C 语言中的 end, _end 符号
使用 man 3 end 可以看到相关符号的解释 这些符号不是在 C 语言文件和头文件中定义的,它们是 ld 在链接所有 .o 文件的时候自己添加的。 end 和 _end 的地址,就是最终程序的堆的起始地址 要打印它们的话,一个样例程序在下面: …...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
