当前位置: 首页 > news >正文

数据结构双向链表

在这里插入图片描述

Hello,好久不见,今天我们讲链表的双向链表,这是一个很厉害的链表,带头双向且循环,学了这个链表,你会发现顺序表的头插头删不再是一个麻烦问题,单链表的尾插尾删也变得简单起来了,那废话不多说,让我们开始我们的学习吧!

首先我们要了解它的物理和逻辑结构,那他们的样子是怎样的呢,首先是一个带头的,那这个难道是我们的哨兵位吗,又是双向,且循环,那让我们来画图了解它吧。

在这里插入图片描述

大致就是这样的一个形状,那我们现在需要这样的一个结构体来实现这样的功能,首先应该有一个存储数据的,就是data,然后就是得有两个指针,一个指向前面的节点,一个就是指向后面的节点,那我们就叫它们一个pre指向前面,一个next指向后面,我们来实现一下吧。

typedef int DListType;
typedef struct DList
{struct DList* pre;struct DList* next;DListType data;}DLNode;

为了方便我们写后面的时候结构体方便一点,我们先定义结构体为DLNode,这样更加方便使用。

现在我们要实现下面的各种接口来完成双链表

首先最重要的就是怎么初始化
初始化的话我们先要想想这个接口函数改的参数和返回值
因为是双向链表,所以得有一个head的头节点,这样才能链接后面的内容

初始化双链表

DLNode* DListInit();

接口函数的名字
这里我们分析首先我们得返回值为什么不是void,而是DLNode*
因为我们要在这里面创建一个头节点,这个节点我们后面都得使用,其次还有一个原因就是这样头节点就不会被销毁,当然我们也可以在外面创建一个节点,然后我们在传它的指针进去,对结构体的内容进行修改,都可以达到相同的作用,废话不多说,我们来实现吧!

DLNode* DListInit()
{DLNode* pHead = (DLNode*)malloc(sizeof(DLNode));assert(pHead);pHead->next = pHead;pHead->pre = pHead;
}

其实很简单,这里必须指针指向自己才可以,如果不这样的话,那我们的循环就不能实现了。
接下来就是怎么打印,打印很简单,我们将它这个指针传过去就行了。

打印双链表

void DListPrint(DLNode* pHead)
{assert(pHead);DLNode* cur = pHead->next;while (cur != pHead){printf("%d ", cur->data);cur = cur->next;}printf("\n");
}

打印函数的想法就是我们找head后面的那个节点,然后打印,因为头节点我们不打印,所以取cur开始,因为是循环,所以cur最后会等于pHead,这样的话就是一个循环,所以我们找下一个就行了,然后打印后更新cur。
接下来我们要创建一个节点,这样才能链接起来我们的节点。

DLNode* CreatNewNode(DListType x)
DLNode* CreatNewNode(DListType x)
{DLNode* newnode = (DLNode*)malloc(sizeof(DLNode));assert(newnode);newnode->data = x;newnode->next = NULL;newnode->pre = NULL;return newnode;
}

创造节点好了,接下来就是尾插一个节点进去,我们前面单链表尾插首先要找到尾的位置,然后再去尾插,同时要先找到尾的前一个位置,然后free尾,这样时间复杂度就是O(N),所以效率不是特别高,那我们现在因为head的位置里存放的就是尾的位置,所以不用进行查找了。我们现在来实现一下吧

双链表尾插

void DListPushBACK(DLNode* pHead, DListType x)
{assert(pHead);DLNode* newnode = CreatNewNode(x);DLNode* pre = pHead->pre;pHead->pre = newnode;newnode->next = pHead;pre->next = newnode;newnode->pre = pre;
}

其实尾插也很简单,我们这里先用一个指针保存位置,这样的话下一次插入就可以找到尾的位置,而且还不用注重顺序,这样大大的提升效率

有了尾插那就肯定有头插,一样的办法我们来实现一下

双链表的头插


void DListPushFront(DLNode* pHead, DListType x)
{assert(pHead);DLNode* newnode = CreatNewNode(x);DLNode* next = pHead->next;pHead->next = newnode;newnode->pre = pHead;newnode->next = next;next->pre = newnode;
}

有了头插这些,那肯定还有头删和尾删
那我们也来实现一下吧

尾删

void DListPopBack(DLNode* pHead)
{assert(pHead);if (pHead->next != pHead){DLNode* del = pHead->pre;del->pre->next = pHead;pHead->pre = del->pre;free(del);}
}

前面写的代码都需要测试一下,我们先来测试三个

#include"DList.h"
void test()
{DLNode* head = DListInit();DListPushBack(head, 1);DListPushBack(head, 2);DListPushBack(head, 3);DListPushBack(head, 4);DListPrint(head);DListPushFront(head, 111);DListPushFront(head, 111);DListPushFront(head, 111);DListPrint(head);DListPopBack(head);DListPopBack(head);DListPopBack(head);DListPrint(head);
}
int main()
{test();return 0;
}

发现我们的程序没有问题,我们再来实现一下头删

void DListPopFront(DLNode* pHead)
{assert(pHead);if (pHead->next != pHead){DLNode* del = pHead->next;pHead->next = del->next;del->next->pre = pHead;free(del);}
}

接下来就是双链表的查找,有了查找我们才能和在任意位置的删除和插入连用,那我们现在来实现一下吧


DLNode* DListFind(DLNode* pHead, DListType x)
{assert(pHead);DLNode* cur = pHead->next;while (cur != pHead){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}

随机插入

void DListInsert(DLNode* pos, DListType x)
{assrt(pos);DLNode* newnode = CreatNewNode(x);DLNode* pospre = pos->pre;pospre->next = newnode;newnode->pre = pospre;newnode->next = pos;pos->pre = newnode;}

也不是随机插入,是在pos位置之前插入,我们直接传pos和x就行,然后在根据之前的想法一步一步的进行插入链接就行

这里可以更新一下头插和尾插,这里就不演示了
那我们在写一个删除pos位置的函数

删除pos位置

void DListPop(DLNode* pos)
{assert(pos);DLNode* pospre = pos->pre;DLNode* posnext = pos->next;pospre->next = posnext;posnext->pre = pospre;free(pos);
}

还有一个destory我们开辟的空间,这个遍历一遍数组, 然后一个一个释放就行,但是会有问题,我们释放的时候必须看要机记住位置,可以从尾巴开始释放,用一个指针记住前面一个的位置,然后释放掉尾巴,然后更新前一个位置,用while控制一下就行了

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int DListType;
typedef struct DList
{struct DList* pre;struct DList* next;DListType data;}DLNode;DLNode* DListInit();void DListPrint(DLNode* pHead);DLNode* CreatNewNode(DListType x);void DListPushBack(DLNode* pHead, DListType x);void DListPushFront(DLNode* pHead, DListType x);void DListPopBack(DLNode* pHead);void DListPopFront(DLNode* pHead);DLNode* DListFind(DLNode* pHead, DListType x);void DListInsert(DLNode* pos, DListType x);void DListPop(DLNode* pos);
#include"DList.h"DLNode* DListInit()
{DLNode* pHead = (DLNode*)malloc(sizeof(DLNode));pHead->next = pHead;pHead->pre = pHead;
}void DListPrint(DLNode* pHead)
{assert(pHead);DLNode* cur = pHead->next;while (cur != pHead){printf("%d ", cur->data);cur = cur->next;}printf("\n");
}DLNode* CreatNewNode(DListType x)
{DLNode* newnode = (DLNode*)malloc(sizeof(DLNode));assert(newnode);newnode->data = x;newnode->next = NULL;newnode->pre = NULL;return newnode;
}void DListPushBack(DLNode* pHead, DListType x)
{DLNode* newnode = CreatNewNode(x);DLNode* pre = pHead->pre;pHead->pre = newnode;newnode->next = pHead;pre->next = newnode;newnode->pre = pre;
}void DListPushFront(DLNode* pHead, DListType x)
{DLNode* newnode = CreatNewNode(x);DLNode* next = pHead->next;pHead->next = newnode;newnode->pre = pHead;newnode->next = next;next->pre = newnode;
}void DListPopBack(DLNode* pHead)
{assert(pHead);if (pHead->next != pHead){DLNode* del = pHead->pre;del->pre->next = pHead;pHead->pre = del->pre;free(del);}
}void DListPopFront(DLNode* pHead)
{assert(pHead);if (pHead->next != pHead){DLNode* del = pHead->next;pHead->next = del->next;del->next->pre = pHead;free(del);}
}DLNode* DListFind(DLNode* pHead, DListType x)
{assert(pHead);DLNode* cur = pHead->next;while (cur != pHead){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}void DListInsert(DLNode* pos, DListType x)
{assrt(pos);DLNode* newnode = CreatNewNode(x);DLNode* pospre = pos->pre;pospre->next = newnode;newnode->pre = pospre;newnode->next = pos;pos->pre = newnode;}void DListPop(DLNode* pos)
{assert(pos);DLNode* pospre = pos->pre;DLNode* posnext = pos->next;pospre->next = posnext;posnext->pre = pospre;free(pos);
}

谢谢大家,我们下期再见

相关文章:

数据结构双向链表

Hello&#xff0c;好久不见&#xff0c;今天我们讲链表的双向链表&#xff0c;这是一个很厉害的链表&#xff0c;带头双向且循环&#xff0c;学了这个链表&#xff0c;你会发现顺序表的头插头删不再是一个麻烦问题&#xff0c;单链表的尾插尾删也变得简单起来了&#xff0c;那废…...

解决政务审计大数据传输难题!镭速传输为政务行业提供解决方案

政务行业是国家治理的重要组成部分&#xff0c;涉及到国家安全、社会稳定、民生福祉等方面。随着信息技术的快速发展和革新&#xff0c;政务信息化也迎来了新一轮的升级浪潮。国家相继出台了《国家信息化发展战略纲要》《“十三五”国家信息化规划》《“十四五”推进国家政务信…...

redis 7高级篇1 redis的单线程与多线程

一 redis单线程与多线程 1.1 redis单线程&多线程 1.redis的单线程 redis单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的&#xff0c;Redis在处理客户端的请求时包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理…...

GO语言:Worker Pools线程池、Select语句、Metex互斥锁详细示例教程

目录标题 一、Buffered Channels and Worker Pools1. Goroutine and Channel Example 线程和通道示例2. Deadlock 死锁3. Closing buffered channels 关闭通道4. Length vs Capacity 长度和容量5. WaitGroup6. Worker Pool Implementation 线程池 二、Select1. Example2. Defau…...

vue ui 创建项目没有反应

问题 cmd中输入 vue ui 没有反应 解决办法 vue ui命令需要vue3.0以上的版本才可以 1、查看当前版本 vue --version vue版本在3.0以下是没有ui命令的 2、查看版本所拥有的命令 vue -h 3、卸载之前版本的vue npm uninstall vue-cli -g 卸载完成&#xff0c;检查是否已经…...

go语言中channel类型

目录 一、什么是channel 二、为什么要有channel 三、channel操作使用 初始化 操作 单向channel 双向channel&#xff0c;可读可写 四、close下什么场景会出现panic 五、总结 一、什么是channel Channels are a typed conduit through which you can send and receive …...

基于STM32F1的电子罗盘HMC5883L角度测量

基于STM32F1的电子罗盘HMC5883L角度测量 参考 1. HMC5883L模块 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Axqqv48y-1692885921487)(…\img\HMC5883L.png)] 型号&#xff1a;GY-271使用芯片&#xff1a;HMCL5883L供电电源&#xff1a;3-5V通…...

Oracle解锁表、包、用户、杀会话、停job

Oracle解锁表、包、用户、杀会话、停job 一、创建包tzq_server_pkg二、授权给需要使用的用户log三、解锁表&#xff1a;执行存过unlock_table(schema_name, table_name)四、解锁包&#xff1a;执行存过unlock_package(schema_name, pkg_name)五、解锁用户&#xff1a;执行存过u…...

软考高级系统架构设计师系列论文九十九:论软件开发平台的选择和应用

软考高级系统架构设计师系列论文九十九:论软件开发平台的选择和应用 一、相关知识点二、摘要三、正文四、总结一、相关知识点 软考高级系统架构设计师系列之:面向构件的软件设计,构件平台与典型架构二、摘要 本文从一个行业MIS系统的开发实践,讨论了软件开发平台的选择和应…...

Redis Pub/Sub 指南

Redis 不仅仅是一个数据库&#xff0c;还可以作为支持发布和订阅&#xff08;Pub/Sub&#xff09;操作的消息代理。本文将使用 Navicat for Redis 简要概述 Redis 的 Pub/Sub 功能。 关于发布或订阅消息范式 Pub/Sub 是一种模式&#xff0c;发送者&#xff08;广播者&#xf…...

Nest(2):Nest 应用目录结构和脚手架命令介绍

Nest 应用目录结构和脚手架命令介绍 在正式使用 NestJS 进行开发之前&#xff0c;先来了解下 Nest 应用的目录结构&#xff0c;和一些常用的脚本命令。 工程目录 下面是使用 nest/cli 创建的 Nest 项目的目录结构。 上篇文章中介绍了 src 目录以及目录下各个文件的作用。下面…...

【嵌入式】MKV31F512VLL12 微控制器 (MCU) 、Cyclone® IV E EP4CE10E22I8LN,FPGA-现场可编程门阵列芯片

1、MKV31F512VLL12 微控制器 (MCU) 是适用于BLDC、PMSM和ACIM电机控制应用的高性能解决方案。这些MCU采用运行频率为100MHz/120MHz、带数字信号处理 (DSP) 和浮点单元 (FPU) 的ARM Cortex-M4内核。KV3x MCU配备两个采样率高达1.2MS/s的16位ADC、多个控制定时器以及512KB闪存。 …...

矢量调制分析基础

前言 本文介绍VSA 的矢量调制分析和数字调制分析测量能力。某些扫频调谐频谱分析仪也能通过使用另外的数字无线专用软件来提供数字调制分析。然而&#xff0c;VSA 通常在调制格式和解调算法配置等方面提供更大的测量灵活性&#xff0c;并提供更多的数据结果和轨迹轨迹显示。本…...

ensp-Ipv6配置配置

ensp-Ipv6配置配置 &#x1f4ce;ipv6.zip&#x1f4ce;Ipv6 网络.docx...

java八股文面试[java基础]—— hashCode 与 equals 区别 == 与 equals的区别

两个对象的hashCode()相同时&#xff0c;equals()相等吗&#xff1f;_两个对象的hashcode一样,equal一样么_不想当个程序员的博客-CSDN博客 equals()&#xff1a;比较的是非基本类型的数据的引用地址&#xff08;即内存地址&#xff09;是否相同&#xff0c;但是对于重写equal…...

Dubbo之PojoUtils源码分析

功能概述 PojoUtils是一个工具类&#xff0c;能够进行深度遍历&#xff0c;将简单类型与复杂类型的对象进行转换&#xff0c;在泛化调用时用到&#xff08;在泛化调用中&#xff0c;主要将Pojo对象与Map对象进行相互转换&#xff09; 功能分析 核心类PojoUtils分析 主要成员…...

【C++】—— C++11新特性之 “右值引用和移动语义”

前言&#xff1a; 本期&#xff0c;我们将要的介绍有关 C右值引用 的相关知识。对于本期知识内容&#xff0c;大家是必须要能够掌握的&#xff0c;在面试中是属于重点考察对象。 目录 &#xff08;一&#xff09;左值引用和右值引用 1、什么是左值&#xff1f;什么是左值引用…...

谈一谈redis脑裂

什么是redis脑裂 &#xff08;1&#xff09;一主多从架构中&#xff0c;主节点与客户端通信正常&#xff0c;主节点与哨兵、从节点连接异常&#xff0c;客户端仍正常写入数据 &#xff08;2&#xff09;哨兵判定主节点下线&#xff0c;重新选主 &#xff08;3&#xff09;原主…...

基于原生Servlet使用模板引擎Thymeleaf访问界面

我们常在Spring Boot项目中使用Thymeleaf模板引擎,今天突发奇想&#xff0c;尝试原生Servlet访问&#xff01; 说做就做 搭建完整的WEB项目 其中的大部分依赖都是后续报错 追加进来的 导入依赖 thymeleaf-3.0.11.RELEASE.jar 第一次访问 访问地址: http://localhost:8080…...

【C语言】15-函数-1

1. 初步认识函数 通过前几章的学习,已经可以编写一些简单的 C 语言程序了,但是如果程序的功能比较多,规模比较大,把所有的程序代码都写在一个主函数(main函数)中,就会使主函数变得庞杂、头绪不清,使阅读和维护程序变得困难。此外,有时程序中要多次实现某一功能就需要…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist

现象&#xff1a; android studio报错&#xff1a; [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决&#xff1a; 不要动CMakeLists.…...

手机平板能效生态设计指令EU 2023/1670标准解读

手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读&#xff0c;综合法规核心要求、最新修正及企业合规要点&#xff1a; 一、法规背景与目标 生效与强制时间 发布于2023年8月31日&#xff08;OJ公报&…...

鸿蒙(HarmonyOS5)实现跳一跳小游戏

下面我将介绍如何使用鸿蒙的ArkUI框架&#xff0c;实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...

自然语言处理——文本分类

文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益&#xff08;IG&#xff09; 分类器设计贝叶斯理论&#xff1a;线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别&#xff0c; 有单标签多类别文本分类和多…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案

引言 在分布式系统的事务处理中&#xff0c;如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议&#xff08;2PC&#xff09;通过准备阶段与提交阶段的协调机制&#xff0c;以同步决策模式确保事务原子性。其改进版本三阶段提交协议&#xff08;3PC&#xf…...

高保真组件库:开关

一:制作关状态 拖入一个矩形作为关闭的底色:44 x 22,填充灰色CCCCCC,圆角23,边框宽度0,文本为”关“,右对齐,边距2,2,6,2,文本颜色白色FFFFFF。 拖拽一个椭圆,尺寸18 x 18,边框为0。3. 全选转为动态面板状态1命名为”关“。 二:制作开状态 复制关状态并命名为”开…...