平衡树与finger search

1.复杂度分析

Treap

定理1:n个节点的Treap的期望深度为o(logn)

证明1:假设所有元素从小到大依次为a1,a2,...,an(不妨假设所有元素各不相同,若有相同可以将这些元素存在同一个位置上),则对于xy,分类讨论:

1.若xy,则xy的祖先等价于ax<minx<jyaj

2.若x>y,则xy的祖先等价于ax<minyj<xaj

(这里要求随机权值构成小根堆,且不妨假设随机权值各不相同)

这件事的概率也就是x权值最小的概率,即1|xy|+1

x的深度也可以定义为x的祖先数,而其期望祖先数即为i=1n1|xi|+1o(logn)

这是论文中给出的证明,但其实并不太严谨,这里再给出一种证明方式——

证明2:T(n)n个节点的Treap的期望深度,考虑枚举其中随机权值最小的点(作为根),即
T(n)=n+i=1nT(i1)+T(ni)n=n+2ni=0n1T(i)
类似地,将n1的式子写出后两式相减,即
T(n)T(n1)=1+2ni=0n1T(i)2n1i=0n2T(i)1+2nT(n1)
简单化简后,即
T(n)n+11n+1+T(n1)n1n+1+1n+T(n2)n1...i=2n+11io(logn)
于是,即T(n)o(nlogn),即所求证

Splay

定理2:对一个V个节点的伸展树执行n次Splay操作的复杂度为o((V+n)logV)

证明:对于一棵伸展树T,定义节点x的势能函数为r(x)=logszx(其中szx为其子树大小),则T的势能函数为φ(T)=xr(x)(其中xT中的节点)

假设第i次操作后的Splay为Ti(特别的,T0为初始的伸展树),第i次操作复杂度(旋转次数)为ai,则第i次操作的均摊复杂度记为bi=ai+R(Ti)R(Ti1),后者记为Δφ(i)

综上,总复杂度即
i=1nai=i=1nbi+R(T0)R(Tn)i=1nbi+VlogV
下面,我们只需要考虑bi即可,对Splay中的三类情况分开讨论:

(为了方便,假设执行Splay(k),且fak的父亲,gak的祖父,带为旋转后的Ti

1.单旋,其使得bi增加1+r(fa)r(k)o(logV)

2.三点一线时,先旋转fa,再旋转k,其使得bi增加
1+r(fa)+r(ga)r(k)r(fa)1+r(k)+r(ga)2r(k)
根据szk=szk+szga+1,有2r(k)r(k)r(ga)log(szk+szga)2szkszga2

2r(k)r(k)r(ga)20加入上式,即有
1+r(k)+r(ga)2r(k)+2r(k)r(k)r(ga)23(r(k)r(k))
3.三点不一线时,旋转两次k,类似地也可以证明其使得bi增加不超过3(r(k)r(k))

注意到r(k)可以与下一次的r(k)相消,因此第2类和第3类总增加量不超过3(r(k)r(k))o(logV)

综上,即对于初始为0的bi,增加总量也不超过o(logV),即bio(logV)

将之代入,不难得到复杂度为o((V+n)logV),即所求证

 

 

 

 

 

2.Treap的可持久化和树套树

可持久化

所有非均摊的数据结构基本都是可以可持久化的

论文中提到了关于Treap中可持久化不能直接复制随机值,而是在比较时进行随机判定是否旋转,并且旋转k的概率为szkszfafak的父亲)

(我觉得或许直接复制随机值应该也是对的吧)

树套树

事实上,旋转的Treap也是可以实现树套树的

定理3:若一次旋转k的复杂度为o(szk),则Treap单次插入操作期望复杂度为o(logn)

证明:假设插入k,当k旋转后k必然是其子树中权值最小的点,这样的概率是1szk,那么这次旋转的期望复杂度即为o(1),也可以看作这个节点对答案的期望贡献为o(1)

又因为前面说明树高为期望o(logn),而只有k到根路径上的点对答案会有期望o(1)的贡献,总复杂度即期望o(logn),结论成立

定理4:若一次重构k子树时间复杂度o(szklogszk),则Treap单次删除期望复杂度为o(logn)

证明:由于Treap是随机的,删除节点的子树大小可以看作一个节点的期望子树大小

由于树高o(logn),因此所有节点子树大小之和为o(nlogn),显然期望子树大小为o(logn),将其子树暴力重构即可,对于o(loglogn)的复杂度可以忽略,即复杂度为期望o(logn)

上面所给的旋转以及重构的复杂度,也就是平衡树套序列的复杂度(每一个节点维护子树内所有元素所构成的序列),也就可以做到o(nlogn)

而对于平衡树套权值线段树,此时插入和删除相较于上面两者也就多了一个logn,复杂度即o(nlog2n),与替罪羊树相同,也是可以接受的

(树套树中的线段树不能互相嵌套,因此重构不能使用线段树合并,仍是o(szklogszklogn)的)

 

 

 

 

 

3.Finger Search

Finger Search

关于Finger Search,即在一个数据结构中,令d(x,y)为在xy之间的元素个数(包括xy),当已经确定x的位置后,可以在o(logd(x,y))的时间内快速查询y的操作

(另外下面还将考虑Finger Search的简单拓展,即快速插入和删除)

显然并不是所有数据结构都能支持Finger Search,下面分别来考虑Treap和Splay

Treap

定理5:在Treap上,xy的路径长度为期望o(logd(x,y))

证明:注意到xy的路径长度即是x祖先且不是y祖先的节点数+是y祖先且不是x祖先的节点数,根据对称性可以仅考虑前者

假设Treap所有元素从小到大依次为a1,a2,...,an,其中ai=xaj=y,显然d(x,y)=ji+1

考虑一个节点ak,求出其满足“是x祖先且不是y祖先”的概率,将其累加即可

根据前面定理1的证明,我们需要对k分类讨论:

1.1ki,这等价于k[k,i]的最小值且[k,i]的最小值大于(i,j]的最小值,前者概率为1ik+1,后者概率为jijk+1,相乘后即ji(ik+1)(jk+1)

注意到这就是1ik+11jk+1,累加后即
k=1i1kk=ji+1j1k=k=1i1k(k=1j1kk=1ji1k)k=1ji1ko(logd(i,j))
2.i<k<j,类似地概率为jk(ki+1)(ji+1)=1ki+11ji+1,累加后即k=1ji+11k1o(logd(i,j))

由此,在Treap上再维护一个子树最小值和最大值,暴力从x向父亲爬去找y即可实现Finger Search

下面来考虑Finger Search的拓展,对于插入:根据定理3,可以发现Treap插入的瓶颈事实上就在于寻找位置,更具体的,我们有以下定理——

定理6:Treap单次插入操作期望旋转次数为o(1)

证明:根据定理3的证明,一个点期望旋转次数,即其到根所有节点子树大小倒数之和

而所有点期望旋转次数之和,考虑一个点的贡献,不难发现恰好为1,即总和期望为o(n),那么其中一点期望次数为o(1),即所求证

那么找到位置后,再以o(1)次旋转即可,即实现了Finger Search的插入

对于删除:Treap的删除需要将该节点旋转到子树叶子,根据定理4这一部分也就是o(loglogn),也可以忽略,那么同样也可以支持删除

综上,Treap需要通过一些技巧来支持Finger Search即其拓展

Splay

在Splay中,由于每一次会将上次所插入、删除或查询的Splay到根,实际上也就是实现了Finger Search,更具体的来说,可以有以下结论——

定理7(Dynamic Finger Theorem):对于一个n个点的Splay,进行m次操作,每一次操作的元素为ai(特别的,a0定义为初始Splay中的根),则复杂度为o(n+m+i=1mlogd(ai1,ai))

这个定理的证明比较复杂,这里就省略了

根据这个结论,也就说明Splay可以不需要附加其他操作来支持Finger Search即其拓展

 

 

 

 

 

4.Treap的快速合并和分裂

合并

考虑合并两颗Treap,分别为T1T2(假设大小分别为nm,且T1中的权值严格小于T2),普通的合并也就是FHQ Treap,合并复杂度为o(logn+logm)

事实上,Treap的合并还可以进一步优化,达到o(logmin(n,m))的复杂度

具体来说(不妨假设n>m),在合并的递归过程中,一开始会又连续较多次都是以T1的根为根并将右儿子与T2合并,这些过程完全可以将最后一次与T2合并

更具体的,考虑从T1中的最大值开始(也就是从根不断向右儿子移动),不断向父亲移动,直至父亲的随机权值小于T2根节点的随机权值时停止,并将当前节点与T2合并并作为父亲的右儿子

(这里的合并就是普通的合并,即做到o(logn+logm)的复杂度)

m显然是不变的,现在来考虑logn,也可以看作最大值移动的次数

最终这个位置将会被T2的根替代,而这条链并不会被压缩,也就是说最终合并后T2的根到T1中最大值的路径长度严格大于移动的次数,根据定理5可以得到是期望o(logm)

n<m类似,也就是会有较多次将T1T2的左儿子合并,同样可以证明复杂度为期望o(logn)

(当然,具体实现中可以更方便的直接自底向上进行合并,这里只是为了说明其实际意义)

分裂

对于一个大小为n+m的Treap(记作T),普通的分裂也是FHQ Treap,分裂复杂度为o(log(n+m))

Treap的分裂也可以优化,假设拆出两颗大小为nm的子树(分别为T1T2,且T1中的权值严格小于T2),则分裂也可以优化到o(logmin(n,m))的复杂度

类似合并,在分裂的过程中,会有很长时间都在向同一边拆分

更具体的,分裂有两种,先来考虑给出排名(也就是nm)的方式:

不妨假设n>m,从最大值出发去找到值所在的位置,根据定理5可以在o(logm)的时间内找到对应权值以及位置,同时还可以求出最大值与该权值的lca

在这个lca之前,显然都会被分到T1,因此可以直接在这个lca内部进行划分

此时递归的深度即与最后T2的深度相同,为期望o(logm)

n<m也是类似的,从最小值去找该值即可

但如果分裂给出的是权值,由于无法确定nm的关系,仅是查找该节点就会退化为o(log(n+m))

这时候还有一个方法,从最小值和最大值同时去找这个权值,且双方每一次各移动一步,那么找到时所花的步数也就是期望o(logmin(n,m)),也就与其相同

启发式合并

启发式合并就不能保证权值有严格的关系,因此对于这样两颗大小为nm的Treap(不妨假设n>m),通常都是以o(mlogn)的复杂度来完成合并的

总得来说,将n个大小为1的Treap以此法启发式合并的复杂度为o(nlog2n)

但是,我们可以将较小的Treap中的权值从小到大插入,此时借助Finger Search的插入拓展,似乎可以优化复杂度,具体来说有以下定理——

定理8:以上述方式启发式合并n个大小为1的Treap,复杂度为o(nlogn)

证明:先来考虑以此法合并大小为nmn>m)的Treap的复杂度,也就可以看作求将n划分为若干个数,每一个数的log之和

根据log函数的凸性,不难调整证明均匀划分时复杂度最低,即单次插入复杂度为o(lognm)

接下来,考虑每一个数的贡献:

假设其执行插入操作k次,第i次插入到的子树大小(指合并结束后)依次为ai(特别的,a0=1),由于ai1恰好是上一次的m,因此其贡献即i=1klogaiai1=logako(logn)

所有节点贡献之和即为o(nlogn),也就是复杂度

关于Splay

在上述操作中,Splay也可以支持部分操作:

1.快速合并,将较小的Splay的最小或最大值Splay到根,虽然这样合并看上去会增加logmax(n,m)的势能,但如果我们修改势能的定义,将其定义为所有非根节点势能和,还是可以做到均摊o(logmin(n,m))的复杂度

2.启发式合并,根据定理7可以证明其与Treap以此法启发式合并的复杂度相同,即也可以做到o(nlogn)

3.对于分裂操作,需要将该节点Splay到根是o(log(n+m))的,似乎无法支持

posted @   PYWBKTDA  阅读(574)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示