【玩转408数据结构】线性表——双链表、循环链表和静态链表(线性表的链式表示 下)
知识回顾
在前面的学习中,我们已经了解到了链表(线性表的链式存储)的一些基本特点,并且深入的研究探讨了单链表的一些特性,我们知道,单链表在实现插入删除上,是要比顺序表方便的,但是,单链表中每个结点仅存在一个指针指向其后续结点,那么如果我们想要找到其前面的节点,则需要从头部开始遍历,这是十分不方便的;那么,是不是对其添加一些元素或特性,使其的操作更加简单呢?那么我们就来看下这节将要学习的一些链表。
双链表
我们知道,单链表中每个结点只有一个指向其后续的指针,这边可以使得单链表可以从前往后的遍历整个链表,但如果我们想要去访问某个结点的前驱节点时,则需要从头开始遍历,这样就会消耗较多的时间;那么为了解决这一问题我们引入双链表。
我们不难观察到,双链表则是在单链表的基础上,在结点中又添加了一个指针,用于指向该结点的前驱结点,那么这样的话,我们也就可以通过一个结点很好的去查询其前驱和后继结点了,虽然我们增加了存储密度,但在对链表的操作上就更加的灵活。
结点初始化:
typedef struct DNode{int data ;struct DNode *prior, *next ;
}DNode, *DLinkList;
双链表的插入
实际上,双链表的插入是类似于单链表的插入的,只不过,由于双链表存在指向后续结点以及前驱结点的指针,所以在进行插入操作时,我们需要对这两个指针分别进行类似的操作即可。
如图所示,我们若要在双链表中插入x,那么我们首先需要让x结点的后续指针指向插入位置后结点,之后再将插入位置后结点(c)的前驱指针指向x;之后再让x结点的前驱指针指向插入位置的前驱结点(a),之后再将插入位置的前驱结点(a)的后继指针指向x;这样我们就成功的将x插入到链表之中了。
需要思考的是,当我们的插入位置为链表的末尾时,我们应该怎么去操作呢?这当然也不难操作,通过对双链表的观察,我们知道,在其末尾结点的后续结点会指向NULL;那么这时就不会存在一个后面的结点指向末尾结点了,也就是说,在之前我们的结点是存在两个指向结点的箭头和两个指出的箭头;但到了末尾就缺少一个指向的箭头。
知道了这层差异,那么我们就要对其进行相应的处理,首先我们需要判断我们插入位置前的结点是否指向NULL,当其指向NULL时,则证明我们插入的为末尾结点,这里我们对前续的操作不变,只不过在对其后续操作时,我们只需要让其指向NULL(也就前末结点指向的位置)即可,不需要进行指向插入结点的操作。
当然,插入首位的方法也同上所示,只不过从特殊处理后续操作转变为特殊处理前驱操作即可。
//插入
bool InsertNextDNode(DNode *p, DNode *s) {if(p == NULL || s == NULL) return false;s->next = p->next;if(p->next != NULL) {p->next->prior = s;}p->next = s;s->prior = p;return true;
}
双链表的删除
对于删除操作,其原理也与单链表的操作类似,只不过其具有两个指针,所以我们需要更改两个指针的指向即可。
对于双俩表的删除操作,例如我们需要删除b结点,那么我们只需要让a的后续指针指向c(b后续指针指向位置),让c的前驱指针指向a(b前驱指针指向位置),这样我们就可以删除b结点了。
例如:删除p指针后的结点b。
p->next=q->next;
q->next->prior=p;
free(q);
当然,删除前驱结点的方法也与之类似,我们更改其相应的指针指向即可。
那我们如果删除的结点位于双链表的头部或者尾部呢?这样对于我们的操作是否存在什么影响?下面我们来看一下:(在这里我们以末结点为例,实际上头结点删除方法与其类似)
当我们遇到尾结点时,由于我们末尾元素后指向NULL,所以其不存在一个指向前的指针,也就是说,如果我们想要删除末尾元素的话,我们就只需要让其前面的元素结点向后指向NULL即可,之后再释放我们想要删除的末尾结点,就可以解决这种问题了。
//删除(p后的节点)
bool DeleteNextDNode(DNode *p) {if(p == NULL) return false;DNode *q = p->next;if(q == NULL) return false;p->next = q->next;if(q->next != NULL) q->next->prior = p;free(q);return true;
}
双链表的遍历
至于遍历双链表这里由于我们前面的学习,这部分的内容就十分的简单了。我们就像单链表遍历一样,通过头指针或头结点开始从头部或者我们所指定的某一点,以此遍历其next指针,直到其指向NULL为止,那么这样我们是不是就可以成功的从头部遍历一遍链表了呢!
乍一听,这与单链表是没什么不同的,但由于我们双链表是一个结点存在两个指针的(也就是指向前的prior以及指向后续的next)指针,所以我们在进行遍历的时候,也就可以去尝试更多的遍历方法了,我们可以尝试从后向前遍历。
这与前面的从前向后遍历并没有什么不同,只是我们需要从尾部向头部遍历的时候需要不停的访问改结点的prior指针,直到其指向NULL为止。
//遍历
//从前向后遍历
void PriorFindList(DLinkList L) {DNode *p = L->next;while(p != NULL) {cout << p->data << " " ;p = p->next;}cout << endl;
}
//从后向前遍历
void AfterFindList(DLinkList L) {DNode *p = L;while(p->next != NULL) {p = p->next;}while(p!=L) {cout << p->data << " " ;p = p->prior;}cout << endl;
}
循环链表
循环链表又可以分为循环单链表和循环双链表。其原理是十分相同的。
循环单链表
通过图我们不难看出,上面的链表是我们已经讨论过多次的单链表,其尾结点指针指向NULL,那么我们怎么使其变成循环链表呢?
循环、循环,顾名思义,就是当我们访问某一序列最后一个元素时,紧接着我们就可以以相同的操作去访问该序列第一个元素;在这里对于链表也是相同的:也就是当我们访问的某链表的尾结点时,我们依旧可以通过常规的访问该结点的next指针的方法去访问到该链表的头结点。
那么这样我们的思路就十分清晰了,我们就只需要在创立链表时,使其的末结点的next指针指向该链表的头结点,这样我们就可以很轻松的实现循环单链表了。
那么我们为什么要学习单链表呢?
如果我们只是使用单链表,当我们有某一结点的位置时,我们可以通过单链表的特性去合理的访问到其后面的各个结点;但其前面的结点对于我们来说就是未知的了。
为了解决这个问题,我们就可以引入循环单链表,这样我们在得到某一结点位置后,我们依次访问最终就可以成功的访问到该链表头结点,那么我们指定结点前的区域就不再是未知的了。
在引入循环链表时,我们就可以设立一个尾指针,甚至说尾指针在这里要比头指针更加的方便!我们不难知道,对于头指针来说访问链表头部需要O(1)的时间复杂度、访问尾部时需要O(n)的时间复杂度。但对于循环单链表的话,我们就可以使用O(1)的时间复杂度访问其头结点和尾结点,对于尾结点没什么好说的,因为其就指向尾结点我们直接访问即可;但对于头结点我们在访问时仅仅需要访问尾结点的next指针,由于其是循环的,所以我们就可以仅用O(1)的时间复杂度就访问到了头结点,这无疑来说节省了很多时间。那么同理,我们在遇到某些操作中包含需要查找尾结点的操作时,这样都可以节省其时间。
循环双链表
如上图中上方的链表一样,该链表是刚才我们已经了解过的双链表;那么其循环双链表的建立实际上是类似于循环单链表的;只不过需要注意的是,由于我们的双链表的每个结点是有两个指针的,所以我们在使其尾部指向头部时,也要去更改头部的prior指针,使其指向链表的尾,这样我们就可以实现循环双链表了。
这里,由于循环链表于前面的单双链表相似,所以这里就不给出其完整代码实现了,实际上我们只需对一些地方的代码进行特定的修改就可以得到该循环链表的代码了。
静态链表
通过前面的学习,我们知道,单链表各个结点的存储,在我们计算机的内存中是完全随机杂乱的,我们需要通过指针去link(连接)这些结点;那么我们可不可以在内存中申请一块连续的存储空间,去进行存储这个链表呢?
当然这乍一听与数组十分的相似,只不过我们需要通过这一连续的存储空间去实现链表的功能,也就是需要通过next"指针"去指向后续节点。
那么这里我们就可以参考数组,我们将结点划分为存数据的data和存下一位置的数组下标next;这样我们在存入一个元素时首先判断该数组位置是否已存入数据,若没有存入数据的话,我们将其存入,并将上一尾结点的next更新为该结点的数组下标。
这里我们通过next去串联数组的下标,进而实现链表功能。
例如上图所示,我们可以将结点的data默认初始化为 -2 (也就是说,当我们在某结点进行存入数据时,可以判断下其next是否为-2,若不是-2则说明该结点已经存在元素),那么我们观察图中链表,可以看出这里我们:头->数组[2]->数组[1]->数组[6]->数组[3]->-1;-1则表明到达了尾部。
优点:增、删 操作不需要大量移动元素 缺点:不能随机存取,只能从头结点开始依次往后查 找;容量固定不可变
适用场景:①不支持指针的低级语言;②数据元素数 量固定不变的场景(如操作系统的文件分配表FAT)
代码
双链表代码
// 2.4 双链表#include <bits/stdc++.h>using namespace std ;typedef struct DNode{int data ;struct DNode *prior, *next ; }DNode, *DLinkList;//初始化 bool InitDLinkList(DLinkList &L) {L = (DNode *)malloc(sizeof(DNode)) ;if(L == NULL) return false ; //分配失败 L->next = NULL;L->prior = NULL;return true; }//尾插法建立 DLinkList DList_TailInsert(DLinkList &L) {int x;cin >> x;DNode *s, *r = L;while(x!=9999) {s = (DNode *)malloc(sizeof(DNode)) ;s->data = x;r->next = s;s->prior = r;r = s;cin >> x;}r->next = NULL;return L; } //插入 bool InsertNextDNode(DNode *p, DNode *s) {if(p == NULL || s == NULL) return false;s->next = p->next;if(p->next != NULL) {p->next->prior = s;}p->next = s;s->prior = p;return true; }//删除(p后的节点) bool DeleteNextDNode(DNode *p) {if(p == NULL) return false;DNode *q = p->next;if(q == NULL) return false;p->next = q->next;if(q->next != NULL) q->next->prior = p;free(q);return true; }//销毁 void DestoryList(DLinkList &L) {while(L->next != NULL){DeleteNextDNode(L);}free(L);L=NULL; } //遍历 //从前向后遍历 void PriorFindList(DLinkList L) {DNode *p = L->next;while(p != NULL) {cout << p->data << " " ;p = p->next;}cout << endl; } //从后向前遍历 void AfterFindList(DLinkList L) {DNode *p = L;while(p->next != NULL) {p = p->next;}while(p!=L) {cout << p->data << " " ;p = p->prior;}cout << endl; }int main() {DLinkList L;InitDLinkList(L);DList_TailInsert(L);PriorFindList(L);AfterFindList(L);DNode *p, *s;p = L;s = L;for(int i=0; i<=3; i++) p = p->next; //删去第五个值 DeleteNextDNode(p);PriorFindList(L);AfterFindList(L);return 0; }
尾:
由于博主才疏学浅,总结的相关知识仅限于自我学习认知,若出现错误。望各位大神指点。在这里感谢各位。
(由于学习的仓促,有些代码未能完全实现以及部分磨块的讲解不充分;若后期实现该代码,将会进行下一步的补充)
相关文章:

【玩转408数据结构】线性表——双链表、循环链表和静态链表(线性表的链式表示 下)
知识回顾 在前面的学习中,我们已经了解到了链表(线性表的链式存储)的一些基本特点,并且深入的研究探讨了单链表的一些特性,我们知道,单链表在实现插入删除上,是要比顺序表方便的,但是…...

分布式概念
分布式概念 一、分布式介绍1.1 分布式计算1.1.1 分布式计算的方法1.1.1 分布式计算与互联网的普及1.1.2 分布式计算项目1.1.3 参与计算 1.2 分布式存储系统1.2.1 P2P 数据存储系统1.2.2 云存储系统 1.3 应用 二、分布式基础概念2.1 微服务2.2 集群2.3 分布式2.4 节点2.5 远程调…...
vue中的ref/reactive区别及原理
Vue中的ref和reactive是两种不同的数据响应式管理方式。 ref是Vue 3中新加入的特性,它可以将一个普通的JavaScript对象转换为响应式对象。通过ref创建的响应式对象在访问和修改时会自动触发重新渲染。ref返回的是一个包含value属性的对象,访问或修改数据…...

深度学习介绍与环境搭建
深度学习介绍与环境搭建 慕课大学人工智能学习笔记,自己学习记录用的。(赋上连接) https://www.icourse163.org/learn/ZUCC-1206146808?tid1471365447#/learn/content?typedetail&id1256424053&cid1289366515人工智能、机器学习与…...

QT C++实践|超详细数据库的连接和增删改查操作|附源码
0:前言 🪧 什么情况需要数据库? 1 大规模的数据需要处理(比如上千上万的数据量)2 需要把数据信息存储起来,无论是本地还是服务上,而不是断电后数据信息就消失了。 如果不是上面的原因化,一般…...

matlab:涉及复杂函数图像的交点求解
matlab:涉及复杂函数图像的交点求解 在MATLAB中求解两个图像的交点是一个常见的需求。本文将通过一个示例,展示如何求解两个图像的交点,并提供相应的MATLAB代码。 画出图像 首先,我们需要绘制两个图像,以便直观地看…...

Unity(第二十二部)官方的反向动力学一般使用商城的IK插件,这个用的不多
反向动力学(Inverse Kinematic,简称IK)是一种通过子节点带动父节点运动的方法。 正向动力学 在骨骼动画中,大多数动画是通过将骨架中的关节角度旋转到预定值来生成的,子关节的位置根据父关节的旋转而改变,这…...
nginx反向代理,获取客户端ip
一、获取客户端ip代码 /*** description: 获取客户端IP* return string*/ public static function getClientIp(){$ip ;if(getenv(HTTP_CLIENT_IP) && strcasecmp(getenv(HTTP_CLIENT_IP),unknown)){$ip getenv(HTTP_CLIENT_IP);}else if(getenv(HTTP_X_FORWARDED_F…...
13 Codeforces Round 886 (Div. 4)G. The Morning Star(简单容斥)
G. The Morning Star 思路:用map记录x,y,以及y-x、yx从前往后统计一遍答案即可公式 a n s c n t [ x ] c n t [ y ] − 2 ∗ c n t [ x , y ] c n t [ y x ] c n t [ y − x ] anscnt[x]cnt[y]-2 * cnt[x,y]cnt[yx]cnt[y-x] anscnt[x]…...

CLion 2023:专注于C和C++编程的智能IDE mac/win版
JetBrains CLion 2023是一款专为C和C开发者设计的集成开发环境(IDE),它集成了许多先进的功能,旨在提高开发效率和生产力。 CLion 2023软件获取 CLion 2023的智能代码编辑器提供了丰富的代码补全和提示功能,使您能够更…...

数据可视化基础与应用-02-基于powerbi实现连锁糕点店数据集的仪表盘制作
总结 本系列是数据可视化基础与应用的第02篇,主要介绍基于powerbi实现一个连锁糕点店数据集的仪表盘制作。 数据集描述 有一个数据集,包含四张工作簿,每个工作簿是一张表,其中可以销售表可以划分为事实表,产品表&am…...

前后端分离Vue+nodejs酒店公寓客房预订管理系统udr7l-java-php-django-springboot
本系统的设计与实现共包含13个表:分别是关于我们信息表,配置文件信息表,公寓信息评论表信息表,公寓入住信息表,公寓退房信息表,公寓信息信息表,公寓预订信息表,系统公告信息表,收藏表…...
VUE打包的dist文件放到后端一起发布
背景 前后端分离开发的项目,在部署时为了方便部署,使用集成部署的方式(即前后端在一起部署的方式) 问题 直接将前端打包好的dist文件夹下的内容,放到后端项目的resource/static目录下,但是在启动访问时发…...

React入门之React_渲染基础用法和class实例写法
渲染元素 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>04元素渲染</title><script src&…...

Git自动忽略dll文件的问题
检查了半天发现是sourcetreee的全局忽略文件导致, 从里面删除dll即可。 我是干脆直接删了全局忽略,太恶心了,如下: #ignore thumbnails created by windows Thumbs.db #Ignore files build by Visual Studio *.exe .vsconfig .s…...
sql中如何实现递归
在SQL中,递归通常是通过使用公用表表达式(Common Table Expressions,CTE)来实现的。CTE允许你定义一个临时的结果集,该结果集可以在一个SELECT、INSERT、UPDATE或DELETE语句的主体中被引用。 递归CTE有两个关键部分&a…...

GPT 的基础 - T(Transformer)
我们知道GPT的含义是: Generative - 生成下一个词 Pre-trained - 文本预训练 Transformer - 基于Transformer架构 我们看到Transformer模型是GPT的基础,这篇博客梳理了一下Transformer的知识点。 BERT: 用于语言理解。(Transformer的Encoder…...
微信小程序 --- 常用样式和组件
常用样式和组件 1. 组件和样式介绍 在开 Web 网站的时候: 页面的结构由 HTML 进行编写,例如:经常会用到 div、p、 span、img、a 等标签 页面的样式由 CSS 进行编写,例如:经常会采用 .class 、#id 、element 等选择…...
深圳智能制造半导体芯片行业源代码防泄密完整解决方案
一、芯片半导体行业防泄密,不能用监控及管控方式来实现,采用管控方式,首先不能主动防御,只能进行事后查询,并且管控方式,不利于嵌入式开发,对于嵌入式开发,不管是采用沙箱隔离或u口禁…...

Unity UI适配规则和对热门游戏适配策略的拆解
前言 本文会介绍一些关于UI适配的基础概念,并且统计了市面上常见的设备的分辨率的情况。同时通过拆解目前市面上较为成功的两款休闲游戏Royal Match和Monopoly GO(两款均为近期游戏付费榜前几的游戏),大致推断出他们的适配策略,以供学习和参…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...

Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

倒装芯片凸点成型工艺
UBM(Under Bump Metallization)与Bump(焊球)形成工艺流程。我们可以将整张流程图分为三大阶段来理解: 🔧 一、UBM(Under Bump Metallization)工艺流程(黄色区域ÿ…...

【java面试】微服务篇
【java面试】微服务篇 一、总体框架二、Springcloud(一)Springcloud五大组件(二)服务注册和发现1、Eureka2、Nacos (三)负载均衡1、Ribbon负载均衡流程2、Ribbon负载均衡策略3、自定义负载均衡策略4、总结 …...
FTXUI::Dom 模块
DOM 模块定义了分层的 FTXUI::Element 树,可用于构建复杂的终端界面,支持响应终端尺寸变化。 namespace ftxui {...// 定义文档 定义布局盒子 Element document vbox({// 设置文本 设置加粗 设置文本颜色text("The window") | bold | color(…...
AT模式下的全局锁冲突如何解决?
一、全局锁冲突解决方案 1. 业务层重试机制(推荐方案) Service public class OrderService {GlobalTransactionalRetryable(maxAttempts 3, backoff Backoff(delay 100))public void createOrder(OrderDTO order) {// 库存扣减(自动加全…...