Splay

article/2025/10/11 8:26:16

Spaly

  众所周知,Splay 是一种平衡二叉查找树(不要告诉我你不知道二叉查找树是什么qwq 不知道什么是二叉查找树的看过来: 关于二叉查找树)。在这篇东西的最后我们也解释了为什么我们需要用到平衡二叉查找树而不是直接查找,这里就不再赘述了,所以现在直接引入正题吧。

旋转操作

  未来解决普通二叉查找树被卡成层数过深而时间复杂度升高的问题我们需要引入这样一个旋转操作。这个操作的本质就是在不改变这棵树的中序遍历的前提下让这颗树的层数减小。具体来说是这样做的(对于左边的那棵树: R R R y y y 的祖先 y y y x x x 的父节点, A A A B B B 分别为 x x x 的左右子树, C C C y y y 的右子树):

在这里插入图片描述

这个图是不是特别好懂呢qwq。 现在我们来解释一下这个神奇的操作吧:顾名思义,旋转就是把一个节点的父节点旋转下来变成这个节点的子节点。但是如果直接旋转下来的话这棵树就变成了一颗三叉树了(下面是如果直接转下来的图):

在这里插入图片描述
  这样搞显然是不行的,所以我们为了保证这棵树还具有二叉查找树的性质,我们就需要把 x x x 原来的右子树 B B B 给从 x x x 上取下来,并且接到 y y y 的左子树上(这样接的目的是保证这个棵树中序遍历不变,读者可以自己写一下旋转后和旋转前的中序遍历验证一下),如下图:

在这里插入图片描述
  这样我们的旋转操作就完成啦。

  然后我们来看看代码吧,其中:

  1. f a [ x ] fa[x] fa[x] 表示 x x x 的父亲节点。
  2. c h [ x ] [ 0 ] ch[x][0] ch[x][0] 就表示 x x x 的左儿子, c h [ x ] [ 1 ] ch[x][1] ch[x][1] 表示 x x x 的右儿子。
  3. get(x) 是个 bool 型函数,如果 x x x 是他父节点的左儿子则返回 0,是右儿子则返回 1。
  4. isr(x) 表示询问 x x x 是不是这棵树的根。
  5. up(x) 就是上传节点信息(这里不用管这个玩意儿啦~~)
inline void rot(int x){int y = fa[x]; int z = fa[y]; int k = get(x);                                    // 先创建变量if(!isr(y)) ch[z][get(y)] = x; fa[x] = z;                                        // 把 x 连在 z 下边 (和 y 同侧)ch[y][k] = ch[x][k ^ 1]; fa[ch[x][k ^ 1]] = y;                                   // 把 x 的一颗子树接下来接到 y 下面ch[x][k ^ 1] = y; fa[y] = x; up(y); up(x);                                       // 把 y 变成 x 的子节点
}

  大家对照这上面第一张旋转操作的图再一句一句得看应该就懂了。

Splay

  splay 顾名思义就是伸展的意思而在代码里面函数 s p l a y ( x ) splay(x) splay(x) 就是用刚才的 r o t a t e rotate rotate 操作将 x x x 一路旋转道根节点的操作。但是我们不能直接 单旋上去 一个 w h i l e while while 循环一直转上去:

inline void spl(int x) { while(!isr(x)) rot(x); }

  就是说这样是绝对不行的,因为这样的时间复杂度不对(至于具体什么原因,等会儿证明时间复杂度的时候会详细说明)。所以我们需要引入双旋的操作来实现把 x x x 转到根的功能。双旋的操作要根据 x x x 和他的父亲和他的父亲的父亲的位置关系来进行操作,一共分为三种情况:

  1. x x x 的父亲就是根节点 R R R

  在这种情况中,我们就只需要转一下 x x x 就行啦。

在这里插入图片描述

  1. x x x x x x 的父亲 y y y x x x 的父亲的父亲 z z z 在同一侧:

  这种情况下,我们要先转一下 y y y 再转一下 x x x

在这里插入图片描述

  1. x x x x x x 的父亲 y y y x x x 的父亲的父亲 z z z 在不同侧:

  在这种情况下,就是说直接转两次 x x x 就行了。

在这里插入图片描述
  对,就是这样。那么我们来看看代码吧:

inline void spl(int x){while(!isr(x)){                                                                           // 直到把 x 转到根int y = fa[x];if(!isr(y)) rot(get(x) ^ get(y) ? x : y);                                             // 三种情况一句写完 qwqrot(x);}
}

  然后 Splay 的其他操作方法就和普通平衡二叉树是类似的,只不过再插入和删除操作中要对当前节点做一遍 Splay 就搞定啦~~~

时间复杂度证明

势能分析

  再说这个东西之前,我们首先要知道一个东西叫做复杂度的势能分析:

  在某些数据结构中我们往往很难估计第 i i i 次操作的时间 t i t_i ti。所以我们要引入势能分析的概念:

  1. φ i \varphi_i φi 表示第 i i i 次操作后数据结构的势能值
  2. 记录 a i = t i + φ i − φ i − 1 a_i = t_i + \varphi_i - \varphi_{i-1} ai=ti+φiφi1 表示第 i i i 次操作的均摊时间

  假设进行了 m m m 次操作,那么总时间就是(很好理解的式子qwq):

T = ∑ i = 1 m t i = ( ∑ i = 1 m a i ) + φ 0 − φ m T = \sum_{i=1}^m t_i = \left(\sum_{i=1}^m a_i\right) + \varphi_0 - \varphi_m T=i=1mti=(i=1mai)+φ0φm

  所以我们只要直到了 a i a_i ai φ i \varphi_i φi 就能知道实际上的总时间了。

Splay 的势能分析s

  在 splay 分析中,我们选取这样一个函数:

  设当前状态下,节点 x x x 的势能值 F ( x ) = log ⁡ 2 s i z e x F(x) = \log_{2}size_x F(x)=log2sizex, 其中 s i z e x size_x sizex 表示这个节点为根的子树的大小。

  设当前状态下,整个 splay 的势能 φ = ∑ i = 1 n F ( i ) \varphi = \sum\limits_{i=1}^n F(i) φ=i=1nF(i)

  那么我们接下来需要证明的就是:

a i ≤ 3 ( F ( x ′ ) − F ( x ) ) + 1 = O ( log ⁡ 2 n s i z e x ) = O ( l o g 2 n ) \begin{aligned} a_i & \leq 3(F(x') - F(x)) + 1 \\ & = O(\log_2\frac{n}{size_x}) = O(log_2 n) \end{aligned} ai3(F(x)F(x))+1=O(log2sizexn)=O(log2n)

  其中 F ( x ) F(x) F(x) F ( x ′ ) F(x') F(x) 分别表示 splay(x) 前和后的势能。因为旋转过后的 x x x 已经到根了,所以就有

F ( x ′ ) = log ⁡ 2 n F(x') = \log_2 n F(x)=log2n

  然后就有:

F ( x ′ ) − F ( x ) = log ⁡ 2 n − log ⁡ 2 s i z e x = log ⁡ 2 n s i z e x F(x') - F(x) = \log_2n - \log_2size_x = \log_2\frac{n}{size_x} F(x)F(x)=log2nlog2sizex=log2sizexn

  然后我们只需要分情况写出三种旋转情况的 a i a_i ai 的值都小于等于 3 ( F ( x ′ ) − F ( x ) ) + 1 3(F(x') - F(x)) + 1 3(F(x)F(x))+1 就能证明双旋 splay 的复杂度是 log ⁡ \log log 级别的了。

  1. 第一种的证明:

在这里插入图片描述

  第一种情况时间就只转了一次,势能变化也很简单,显然只有被转的节点 x x x 和他的爸爸 R R R 的势能变了。所以我们就能得到:

a z i g = t i + φ i − φ i − 1 = 1 + F ( x ′ ) + F ( R ′ ) − F ( x ) − F ( R ) = 1 + F ( R ′ ) − F ( x ) ≤ 3 ( F ( x ′ ) − F ( x ) ) + 1 \begin{aligned} a_{zig} & = t_i + \varphi_i - \varphi_{i-1} \\ & = 1 + F(x') + F(R') - F(x) - F(R) \\ & = 1 + F(R') - F(x) \leq 3(F(x') - F(x)) + 1 \end{aligned} azig=ti+φiφi1=1+F(x)+F(R)F(x)F(R)=1+F(R)F(x)3(F(x)F(x))+1

  显然在上图中 F ( x ′ ) F(x') F(x) F ( R ) F(R) F(R) 是相等的。所以最后一个等式是相等的。

  1. 然后是第二种情况的证明:

在这里插入图片描述
  第二种情况也就值转了 2 次,势能变化就有点麻烦了,多了一个 z z z 的势能发生了变化。所以我们得到:

a z i g − z i g = t i + φ i − φ i − 1 = 2 + F ( x ′ ) + F ( y ′ ) + F ( z ′ ) − F ( x ) − F ( y ) − F ( z ) = 2 + F ( y ′ ) + F ( z ′ ) − F ( x ) − F ( y ) \begin{aligned} a_{zig-zig} & = t_i + \varphi_i - \varphi_{i-1} \\ & = 2 + F(x') + F(y') + F(z') - F(x) - F(y) - F(z) \\ & = 2 + F(y') + F(z') - F(x) - F(y) \end{aligned} azigzig=ti+φiφi1=2+F(x)+F(y)+F(z)F(x)F(y)F(z)=2+F(y)+F(z)F(x)F(y)

  观察上面的图我们发现 F ( x ′ ) F(x') F(x) 显然等于 F ( z ) F(z) F(z)。所以最后一个等式成立。然后现在这个式子看着这个 2 是不是非常的难受,所以我们继续观察看看能不能处理掉这个 2。于是我们注意到:

F ( x ) + F ( z ′ ) − 2 F ( x ′ ) = l o g 2 s i z e x ⋅ s i z e z ′ s i z e x ′ 2 ≤ l o g 2 1 4 = − 2 \begin{aligned} F(x) + F(z') - 2F(x') = log_2 \frac{size_x \cdot size_{z'}}{size_{x'}^2} \leq log_2\frac14 = -2 \end{aligned} F(x)+F(z)2F(x)=log2sizex2sizexsizezlog241=2

  这个式子如果看不懂的话就数一下上图的点数算算每个点的 size 然后带到式子里看一下就应该能懂了qwq(其实就是我懒得解释)。

  然后带回前面的式子:

a z i g − z i g ≤ 2 F ( x ′ ) + F ( y ′ ) − 2 F ( x ) − F ( y ) ≤ 3 ( F ( x ′ ) − F ( x ) ) + 1 a_{zig-zig} \leq 2F(x') + F(y') - 2F(x) - F(y) \leq 3(F(x') - F(x)) + 1 azigzig2F(x)+F(y)2F(x)F(y)3(F(x)F(x))+1

  1. 第三种情况的证明:

在这里插入图片描述

  时间仍然是转了两次,是能也仍然是 x , y , z x,y,z x,y,z 都发生了变化,那么:

a z i g − z a g = t i + φ i − φ i − 1 = 2 + F ( x ′ ) + F ( y ′ ) + F ( z ′ ) − F ( x ) − F ( y ) − F ( z ) = 2 + F ( y ′ ) + F ( z ′ ) − F ( x ) − F ( y ) \begin{aligned} a_{zig-zag} & = t_i + \varphi_i - \varphi_{i-1} \\ & = 2 + F(x') + F(y') + F(z') - F(x) - F(y) - F(z) \\ & = 2 + F(y') + F(z') - F(x) - F(y) \end{aligned} azigzag=ti+φiφi1=2+F(x)+F(y)+F(z)F(x)F(y)F(z)=2+F(y)+F(z)F(x)F(y)

  然后仍然是注意到:

F ( x ) + F ( z ′ ) − 2 F ( x ′ ) = l o g 2 s i z e x ⋅ s i z e z ′ s i z e x ′ 2 ≤ l o g 2 1 4 = − 2 \begin{aligned} F(x) + F(z') - 2F(x') = log_2 \frac{size_x \cdot size_{z'}}{size_{x'}^2} \leq log_2\frac14 = -2 \end{aligned} F(x)+F(z)2F(x)=log2sizex2sizexsizezlog241=2

  然后仍然是两个式子进行合并:

a z i g − z a g ≤ 2 F ( x ′ ) + F ( y ′ ) − 2 F ( x ) − F ( y ) ≤ 3 ( F ( x ′ ) − F ( x ) ) + 1 a_{zig-zag} \leq 2F(x') + F(y') - 2F(x) - F(y) \leq 3(F(x') - F(x)) + 1 azigzag2F(x)+F(y)2F(x)F(y)3(F(x)F(x))+1

  综上所述,三种情况中我们都有:

a i ≤ 3 ( F ( x ′ ) − F ( x ) ) + 1 = O ( log ⁡ 2 n ) a_i \leq 3(F(x') - F(x)) + 1 = O(\log_2 n) ai3(F(x)F(x))+1=O(log2n)

  所以 a i = log ⁡ 2 n a_i = \log_2n ai=log2n,所以:

T = ( ∑ i = 1 n a i ) + φ 0 − φ m = O ( m log ⁡ 2 n ) + φ 0 − φ m T = \left(\sum_{i=1}^n a_i\right) + \varphi_0 - \varphi_m = O(m\log_2n) + \varphi_0 - \varphi_m T=(i=1nai)+φ0φm=O(mlog2n)+φ0φm

  又因为 0 ≤ φ ≤ n log ⁡ 2 n 0 \leq \varphi \leq n\log_2 n 0φnlog2n,所以 φ 0 − φ m = O ( n log ⁡ 2 n ) \varphi_0 - \varphi_m = O(n\log_2n) φ0φm=O(nlog2n),所以:

T = O ( m log ⁡ 2 n ) + O ( n log ⁡ 2 n ) = O ( ( n + m ) log ⁡ 2 n ) T = O(m\log_2n) + O(n\log_2n) = O((n + m)\log_2n) T=O(mlog2n)+O(nlog2n)=O((n+m)log2n)

  于是双旋 splay 的时间复杂度就是 O ( ( n + m ) log ⁡ 2 n ) O((n + m)\log_2n) O((n+m)log2n) 了。


http://chatgpt.dhexx.cn/article/9edsN4iS.shtml

相关文章

aplay 源码分析

ffmpeg -formats ffmpeg -sample_fmts ffmpeg -i ../english14.mp3 -ar 44100 -ac 2 -sample_fmt s16 -f wav english14.wav ffmpeg -i ../english14.mp3 -ar 44100 -ac 2 -sample_fmt s16 -f s16le english14.pcm其中针对PCM个数的数据aplay正确的播放格式为: apl…

Qt,Linux: 播放声音(aplay)

Linux下,Qt开发,使用的电脑情况比较复杂,开发机是Intel cpu, 常用的验证机是飞腾(arm)cpu, 客户的目标机也是飞腾(arm)cpu, 但验证机和目标机上情况还不太一样。 需要用到播放声音的功能&#x…

ALSA音频架构 -- aplay播放流程分析

引言 上文ALSA音频架构 – snd_pcm_open函数分析已经获取了aplay中对应的播放API writei_func snd_pcm_writei;,本文将具体分析该API在哪里调用。 aplay处理数据流顺序图 代码详细分析 1、对参数进行解析,存储在全局变量hwparams,配置回调…

《Autosar_MCAL高阶配置》总目录_培训教程持续更新中...

欢迎大家订阅《Autosar_MCAL高阶配置》专栏(可以理解为是Autosar培训教程),献上常用的案例和配置方法。下方整理了相关博文的链接(单击蓝色字体即可跳转),方便大家获取。 本专栏旨在: 扫除Auto…

Autosar MCAL-ADC配置PWM硬件触发采样

文章目录 前言ADC配置AdcGroupRequestSourceAdcGroupTriggSrcAdcHwExtTrigSelectAdcHwGatePinAdcGeneral-AdcHwTriggerApiAdcHwGateSignalAdcHwTrigSignalAdcHwTrigType GtmGtmConnections PWM实际使用总结 前言 在实际项目开发过程中,关于ADC采样,大部…

【学一点英飞凌】AutoSar-MCAL-Gtm-TOM模块

系列文章目录 【英飞凌学习笔记】TC3XX系列GTM模块的基本组成 文章目录 系列文章目录前言一、TOM模块是什么?二、TGC Sub-unit-Global Channel Control(全局控制寄存器)1、触发源2、触发方法3、注意要点4、TGC具体结构 总结 前言 提示&…

【MCAL_CANDriver】-1.5-图解CANFD如何兼容经典Classical CAN 2.0及其解决方案

点击返回「《Autosar_MCAL高阶配置》总目录」 目录 1 图解CANFD网络兼容Classical CAN 1.1 Classical CAN节点接收CANFD帧检出错误原因 1.2 CAN FD升级解决方案选择 2 CANFD对硬件设计要求 END 1 图解CANFD网络兼容Classical CAN 关于CANFD帧和Classical CAN帧结构差异&…

基于EB工具的TC3xx_MCAL配置开发01_WDG模块配置介绍

目录 1.概述2. WDG 配置2.1 General部分配置2.2 WdgSettingsConfig配置2.2.1 配置概述2.2.2 CPU WDG具体配置2.3 WdgDemEventParameterRefs3. WDG配置注意事项1.概述 本篇开始我们基于EB Tresos工具对英飞凌TC3xx系列MCU的MCAL开发进行介绍,结合项目经验对各MCAL外设的开发及…

MCAL 部分配置详情

对CSDN上的MCAL相关文章配置的记录 ,持续更新 英飞凌: Port 模块 DIO: DIO可以看做是Port的使用接口,应用程序是不可以直接操作Port或Pin的,只能通过DIO。DIO的配置也比较简单,其实就是给Pin重新定义了一个名称。 …

Autosar MCAL-ICU输入捕获

文章目录 前言ICUIcuChannelIcuChannelIdIcuDefaultStartEdgeIcuMeasurementModeIcuSignalTypeIcuWakeupCapability子配置项IcuSignalMeasurementIcuSignalMeasurementPropertyIcuDutycycleBufferMarker IcuOptionalApisIcuGetDutyCycleValuesApiIcuSetModeApiIcuSignalMeasure…

Autosar MCAL-SPI配置及使用

文章目录 前言SPI协议基础Autosar SPI专有名词SpiDriverSpiChannelSpiChannelIdSpiChannelTypeSpiDataWidthSpiDefaultDataSpiEbMaxLengthSpiIbNBuffersSpiTransferStart SpiExternalDeviceSpiBaudrateSpiAutoCalcBaudParamsSpiCsIdentifierSpiCsPolaritySpiCsSelectionSpiData…

【AUTOSAR】 MCAL配置说明(二)----MCAL CAN模块配置

CAN CAN 通讯模块配置 主要的配置内容如下: can 模块的时钟源 can busoff, rx, tx的处理方式 映射pin 脚选择 波特率,采样点设置 邮箱配置,接收、发送报文邮箱,标准帧/扩展帧 CanClockConfiguration 配置CAN 模…

Autosar MCAL-ADC详解(二)-基于Tc27x的cfg软件

文章目录 前言AdcHwUnitAdcGroupChannel Emux SelectGroup Access Mode配置结果指针初始化结果访问读取buffer中的结果 Group Buffer MarkerGroup Conversion ModeGroup IdGroup PriorityGroup ReplacementGroup Request SourceAdcGroupTriggSrcAdcHwTrigSignalHw Gate SignalA…

TC397 EB MCAL开发从0开始系列 之 [1.0]-MCAL结构及Demo介绍

MCAL结构介绍MCAL结构MCAL模块 MCAL安装包说明DemoWorkspaceMclsar EB 环境配置EB环境安装EB新建工程 ->返回总目录-< MCAL结构介绍 MCAL结构 MCAL是 Microcontroller Abstraction Layer&#xff08;微控制器抽象层)的简写&#xff0c;是AuoSar架构中的概念&#xff0…

MCAL中ADC的配置

根据硬件资源分配以及各信号的应用对ADC模块进行配置,使能正确采集信号,并提供转换结果。 1. ADC模块接口配置 使能AdcHwTriggerApi:硬件触发ADC转换,根据硬件需求,部分Channel的转换是通过硬件信号触发转换,因此需要使能该API。 使能AdcEnableStartStopGroupApi:软件…

AurixDevStudio集成MCAL

这是Tricore MCAL安装路径 打开ADS新建一个AURIX Project 我手上的是龙邱的TC377最小系统, 就这样选 理解下第一个选项 新建好的基础工程是这个样子 删除掉Library文件夹, 因为我们这里要使用的MCAL而不是iLLD库(虽然它们实现的功能大体相同) 在工程里新建一个文件夹为Mca…

AUTOSAR 学习笔记(一):NXP S32K14X AUTOSAR MCAL 软件下载及安装

AUTOSAR学习笔记(一)&#xff1a;NXP S32K14X AUTOSAR MCAL 软件下载及安装 目录 AUTOSAR学习笔记(一)&#xff1a;NXP S32K14X AUTOSAR MCAL 软件下载及安装1.下载MCAL 软件2、下载 AUTOSAR EB tresos 软件3、下载AUTOSAR 4.0 OS4、安装 EB tresos5、安装 MCAL6、安装 AUTOSAR…

[ 搞一点AutoSar ]基于EB的MCAL-GPT全模块配置与解析

笔者搞了快一个星期的GPT的测试了&#xff0c;从配置到代码一遍又一遍的操作和阅读。觉得有必要把学习成功稍微总结一下了&#xff1b;学AUTOSAR最后还是得熟悉代码&#xff0c;毕竟AUTOSAR只是目的&#xff0c;而代码才是实现的手段。中间的逻辑关系看代码一目了然&#xff1b…

【MCAL_CANDriver】-1.3-FullCAN和BasicCAN的差异及配置使用

点击返回「《Autosar_MCAL高阶配置》总目录」 目录 1 什么是FullCAN和BasicCAN 1.1 FullCAN / Basic CAN HRH区别 1.2 FullCAN / Basic CAN HTH区别 1.3 FullCAN和Basic CAN存在的原因 1.4 FullCAN/Basic CAN HRH/HTH如何选择 2 如何配置FullCAN和BasicCAN 3 来自CAN Dr…

MCAL MCU Module详解和配置说明

关注“嵌入式软件实战派”回复“AUTOSAR”获得更多实战教程。 以下内容包含&#xff1a;基本概念、模块依赖、应用时序、参数配置实践讲解&#xff0c;以及ECUM对其引用等。 1. 基本概念 描述了MCU&#xff08;Microcontroller Unit&#xff09; 驱动程序的功能和 API。 MCU 驱…