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

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

Razor编程中@Html的方法使用大全
文章目录 1. 基础HTML辅助方法1.1 Html.ActionLink()1.2 Html.RouteLink()1.3 Html.Display() / Html.DisplayFor()1.4 Html.Editor() / Html.EditorFor()1.5 Html.Label() / Html.LabelFor()1.6 Html.TextBox() / Html.TextBoxFor() 2. 表单相关辅助方法2.1 Html.BeginForm() …...

iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...

逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...
作为测试我们应该关注redis哪些方面
1、功能测试 数据结构操作:验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化:测试aof和aof持久化机制,确保数据在开启后正确恢复。 事务:检查事务的原子性和回滚机制。 发布订阅:确保消息正确传递。 2、性…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...