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 的地址,就是最终程序的堆的起始地址 要打印它们的话,一个样例程序在下面: …...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...

跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...