数据结构(初阶)(四)----双向链表

  • 双向链表
    • 初始化
    • 尾插
    • 打印
    • 尾删
    • 头插
    • 头删
    • 查找
    • 在pos位置之后插入数据
    • 在pos位置之前插入数据
    • 删除pos结点
    • 销毁链表

双向链表

链表分类:8种(2*2*2)

带头,不带头

单向,双向

循环,不循环

最常用的是两种:

单链表:不带头,单向,不循环链表

双链表:带头,双向,循环链表

带头链表⾥的头结点,实际为“哨兵位”,哨兵位结点不存储任何有效元素只是站在这⾥占位置,类似于“放哨的”。

在这里插入图片描述

双向链表由结点组成:结点中包含数据,指向下一个结点的指针以及指向前一个结点的指针

struct ListNode
{int data;struct ListNode* next;struct ListNode* prev;
}

初始化

单链表为空链表时,那么链表中没有节点,plist就指向NULL

双向链表为空时,此时链表只有一个头结点(哨兵位),如果没有哨兵位,那么它就不能被称为双向链表,于是我们在对双向链表初始化时,一定要创建一个哨兵位。

思考:在初始化时,是否可以将哨兵位的prev,next指针初始化为NULL?

在这里插入图片描述

答案是绝对不可以。

应该将哨兵位的prev,next指针都指向它自己。

在这里插入图片描述

#include"List.h"
//申请结点
LTNode* LTBuyNode(x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail");exit(1);}newnode->data = x;newnode->prev = newnode->next = newnode;return newnode;
}
//以参数的形式返回哨兵位
//创建链表,创建一个哨兵位
//void LTInit(LTNode** pphead)
//{
//	*pphead = LTBuyNode(-1);
//}//以返回值的形式返回哨兵位,推荐使用
//创建链表,创建一个哨兵位
LTNode* LTInit()
{LTNode* phead = LTBuyNode(-1);return phead;
}

调用的区别

参数形式的调用

List是指向节点的指针,节点指针,它存储的内容是结点的地址
//LTNode* plist = NULL;
&pList,是取指向节点的指针的地址,这样就可以修改指向节点的指针的地址
//LTInit(&plist);

返回值形式的调用

//在初始化中,创建节点,并返回指向节点的指针(推荐使用,原因末尾会讲)
LTNode* List = LTInit();

尾插

首先我们需要知道:哨兵位结点不能被删除,结点的地址也不能发生改变
哨兵位不能为空,否则链表无效,再操作就没有用了
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTNode* newnode = LTBuyNode(x);newnode->prev = phead->prev;newnode->next = phead;phead->prev->next = newnode;phead->prev = newnode;
}

如果只有哨兵位,代码是否成立?

成立!
在这里插入图片描述

注意:

在这里插入图片描述

不能完全交换

如果交换,d3的地址丢失,代码会出问题

如果非要交换,可以先将d3的地址存储起来,保证之后可以正常使用

打印

哨兵位不是有效结点,直接从哨兵位的下一个结点打印,直到循环到哨兵位结束
在这里插入图片描述

//打印链表
void LTPrint(LTNode* phead)
{//哨兵位不再打印LTNode* pcur = phead->next;while (pcur != phead){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}

尾删

1,首先我们在进行删除操作时,需要保证链表有效且不能为空(即只有一个哨兵位时)。
在这里插入图片描述

2,先找到最后一个结点位置,即phead->next;

将该节点保存下来,以便后续使用。

3,将最后一个结点的prev节点与哨兵位结点联系起来,使之成为新的最后结点

4,释放del节点,并置空。

//尾删
void LTPopBack(LTNode* phead)
{//assert(phead && phead->prav != phead);assert(phead && phead->next != phead);LTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del = NULL;
}

头插

插入到第一个有效结点之前,即哨兵位后面

操作和尾插极其相似,不再赘述。

在这里插入图片描述

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{//链表要有效assert(phead);LTNode* newnode = LTBuyNode(x);newnode->prev = phead;newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;
}

头删

和尾删非常相似。

在这里插入图片描述

//头删
void LTPopFront(LTNode* phead)
{assert(phead && phead->next != phead);LTNode* del = phead->next;del->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}

查找

遍历双向链表

注意,循环条件是pcur != phead,因为双向链表是循环链表,如果不加该条件,循环将会一直成立,这点与单链表不同。

//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{LTNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur->next;}return NULL;
}

在pos位置之后插入数据

在这里插入图片描述

先修改新结点,不会影响原链表的结点

//在指定位置pos之后插入数据
void LTInsertBack(LTNode* pos, LTDataType x)
{assert(pos);LTNode* newnode = LTBuyNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}

在pos位置之前插入数据

与在之后插入数据相似。

不再详细写。

删除pos结点

//删除pos结点
void LTErase(LTNode* pos)
{//pos理论上来说不能为phead,但是此处并没有传phead,无法校验assert(pos);pos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);pos = NULL;
}

要将pos结点的空间释放并置空,为什么在这里不传二级指针?

在理论上参数是要传二级指针的,因为我们需要让形参的改变影响实参,

没有这样做的原因是为了保持接口的一致性

在双向链表的方法中传的都是一级指针,可以大大降低使用方法的记忆成本。

正确解决方法是,在测试文件中调用方法后,将find置空

这就解释了为什么我们在最开始对双向链表初始化时,要以返回值的形式返回哨兵位。

销毁链表

与删除不同的是,销毁;链表要删除哨兵位。

此处依旧传一级指针,原因是保持接口一致性,统一参数,解决方法与上面一样。

在这里插入图片描述

//销毁链表
void LTDesTroy(LTNode* phead)
{LTNode* pcur = phead->next;while (pcur != phead){LTNode* next = pcur->next;free(pcur);pcur = next;}//此时pcur已经循环到phead,即pcur指向phead,phead也要销毁free(phead);phead = NULL;
}

调试
在这里插入图片描述

跳出函数后,pcur销毁,所以在函数内部不置空也没有问题

但是调用时,plist没有置空,需要手动置空

不同点顺序表链表(单链表)
存储空间上物理上⼀定连续逻辑上连续,但物理上不⼀定连续
随机访问⽀持:O(1)不⽀持:O(N)
任意位置插⼊或者删除元素可能需要搬移元素,效率低O(N)只需修改指针指向
插⼊动态顺序表,空间不够时需要扩 容和空间浪费动态顺序表,空间不够时需要扩 容和空间浪费
应⽤场景元素⾼效存储+频繁访问任意位置⾼效插⼊和删除

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/25965.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python-leetcode-寻找重复数

287. 寻找重复数 - 力扣(LeetCode) class Solution:def findDuplicate(self, nums: List[int]) -> int:# Step 1: 找到环的相遇点slow nums[0]fast nums[0]# 使用快慢指针,直到相遇while True:slow nums[slow] # 慢指针走一步fast nu…

赋能农业数字化转型 雏森科技助力“聚农拼”平台建设

赋能农业数字化转型,雏森助力“聚农拼”平台建设 在数字化浪潮席卷各行业的今天,农业领域也在积极探索转型升级之路。中农集团一直以“根植大地,服务三农”为核心,以“乡村振兴,农民增收”为目标,及时响应…

自然语言处理:词频-逆文档频率

介绍 大家好,博主又来给大家分享知识了。本来博主计划完成稠密向量表示的内容分享后,就开启自然语言处理中文本表示的讲解。可在整理分享资料的时候,博主发现还有个知识点,必须得单独拎出来好好说道说道。 这就是TF-IDF&#xf…

如何修改安全帽/反光衣检测AI边缘计算智能分析网关V4的IP地址?

TSINGSEE青犀推出的智能分析网关V4,是一款集成了BM1684芯片的高性能AI边缘计算智能硬件。其内置的高性能8核ARM A53处理器,主频可高达2.3GHz,INT8峰值算力更是达到了惊人的17.6Tops。此外,该硬件还预装了近40种AI算法模型&#xf…

原生家庭独立的艺术:找到自我与家庭的平衡点

原生家庭独立的艺术:找到自我与家庭的平衡点 🌱 引言 🌈 小林刚刚和父母结束了一次激烈的电话对峙。父母坚持认为他应该回到家乡工作,“这样我们也能照顾你”,而他则努力解释自己在大城市的职业规划。挂掉电话后&…

flink系列之:使用flink cdc3从mysql数据库同步数据到doris和starrocks

flink系列之:使用flink cdc3从mysql数据库同步数据到doris和starrocks 一、下载部署flink二、下载部署flink cdc3三、下载mysql-connector-java到flink和flink cdc的lib目录四、flink设置checkpoint支持增量同步数据五、mysql到doris和starrocks的yaml配置文件六、启…

xr-frame 3D Marker识别,扬州古牌坊 3D识别技术稳定调研

目录 识别物体规范 3D Marker 识别目标文件 map 生成 生成任务状态解析 服务耗时: 对传入的视频有如下要求: 对传入的视频建议: 识别物体规范 为提高Marker质量,保证算法识别效果,可参考Marker规范文档 Marker规…

【pytest框架源码分析一】pluggy源码分析之hook常用方法

简单看一下pytest的源码,其实很多地方是依赖pluggy来实现的。这里我们先看一下pluggy的源码。 pluggy的目录结构如下: 这里主要介绍下_callers.py, _hooks.py, _manager.py,其中_callers.py主要是提供具体调用的功能,_hooks.py提…

一周学会Flask3 Python Web开发-Jinja2模板过滤器使用

锋哥原创的Flask3 Python Web开发 Flask3视频教程: 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 在Jinja2中,过滤器(filter)是一些可以用来修改和过滤变量值的特殊函数,过滤器和变量用一个竖线 | &a…

【官方配图】win10/win11 安装cuda 和 cudnn

文章目录 参考资料1.安装cuda toolkit1. 下载安装包2.安装验证 2. 安装cudnn下载cudnn安装包安装cudnn安装后的配置 参考资料 官方nvidia安装cuda官方nvidia安装cudnn 1.安装cuda toolkit 1. 下载安装包 下载地址 https://developer.nvidia.com/cuda-downloads?target_osW…

Linux Mem -- 关于AArch64 MTE功能的疑问

目录 1.虚拟地址和物理地址映射完成后,才可以设置虚拟地址对应的memory tag ? 2.各种memory allocator中的address tag从哪来,怎么产生? 2.1 vmalloc allocator 2.2 slub分配器 2.3 用户可以指定IRG指令产生的address tag 3.kasan…

FS800DTU联动OneNET平台数据可视化View

目录 1 前言 2 环境搭建 2.1 硬件准备 2.2 软件环境 2.3 硬件连接 3 注册OneNET云平台并建立物模型 3.1 参数获取 3.2 连接OneNET 3.3上报数据 4 数据可视化View 4.1 用户信息获取 4.2 启用数据可视化View 4.3 创建项目 4.4 编辑项目 4.5 新增数据源 4.6 数据过滤器配置 4.6 项…

vscode脚本 shell 调试

插件,按照图片

纯代码实战--用Deepseek+SQLite+Ollama搭建数据库助手

如何用Python调用本地模型实现DeepSeek提示词模板:一步步教你高效解决13种应用场景 从零到一:纯代码联合PyQt5、Ollama、Deepseek打造简易版智能聊天助手 用外接知识库武装大模型:基于Deepseek、Ollama、LangChain的RAG实战解析 纯代码实战–…

Qt监控系统远程回放/录像文件远程下载/录像文件打上水印/批量多线程极速下载

一、前言说明 在做这个功能的时候,着实费了点心思,好在之前做ffmpeg加密解密的时候,已经打通了极速加密保存文件,主要就是之前的类中新增了进度提示信号,比如当前已经处理到哪个position位置,发个信号出来…

《Qt动画编程实战:轻松实现头像旋转效果》

《Qt动画编程实战:轻松实现头像旋转效果》 Qt 提供了丰富的动画框架,可以轻松实现各种平滑的动画效果。其中,旋转动画是一种常见的 UI 交互方式,广泛应用于加载指示器、按钮动画、场景变换等。本篇文章将详细介绍如何使用 Qt 实现…

AIGC生图产品PM必须知道的Lora训练知识!

hihi,其实以前在方向AIGC生图技术原理和常见应用里面已经多次提到Lora的概念了,但是没有单独拿出来讲过,今天就耐心来一下! 🔥 一口气摸透AIGC文生图产品SD(Stable Diffusion)! 一、…

Spring Boot 3.x 基于 Redis 实现邮箱验证码认证

文章目录 依赖配置开启 QQ 邮箱 SMTP 服务配置文件代码实现验证码服务邮件服务接口实现执行流程 依赖配置 <dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spr…

QT day1

作业 代码 class Widget: public QWidget {QPushButton* button; //按钮Widget* other; //显示对面 public:Widget(){button new QPushButton("按钮",this); //控件 认this作父this->resize(300,300); //界面大小button->resize(100,10…

Go红队开发—语法补充

文章目录 错误控制使用自定义错误类型错误包装errors.Is 和 errors.Aspanic捕获、recover 、defer错误控制练习 接口结构体实现接口基本类型实现接口切片实现接口 接口练习Embed嵌入文件 之前有师傅问这个系列好像跟红队没啥关系&#xff0c;前几期确实没啥关系&#xff0c;因为…