本文用势能法证明\(Splay\)的均摊复杂度,对\(Splay\)的具体操作不进行讲述。

为了方便本文的描述,定义如下内容:

在文中我们用\(T\)表示一棵完整的\(Splay\),并(不严谨地)用\(|T|\)表示\(T\)这棵\(Splay\)的节点数目。

如无特殊说明,小写英文字母(如\(x\),\(y\),\(z\))在本文中表示\(T\)的一个节点,并(不严谨地)用\(|x|\)表示以节点\(x\)为根的子树的大小,\(x\in T\)表示节点\(x\)在\(T\)中。

一般我们默认\(x'\)代表节点\(x\)在经过了上下文中描述的操作以后的状态,因此对应的\(x\)代表之前的状态。

我们用\(\Phi(T)\)表示整棵\(Splay\)的势能函数,\(\phi(x)\)则表示节点\(x\)对\(T\)贡献的势能。

=============================================

先来讲一下我们的势能函数,我们定义:

\[\phi(x)=\log|x|\]

\[\Phi(T)=\sum_{x\in T}\phi(x)\]

可以发现,对于任意时刻,因为\(|x|\geq 1\),因此\(\log|x|\geq 0\),从而得到\(\Phi(T)\geq 0\),因此势能函数是合法的。同时\(\forall |x|\leq |T|\),因此我们总有\(\Phi(T)\leq |T|\log|T|\)。这个上界是比较松的,但是对我们的分析没有影响。

下面考虑一次伸展操作对于势能函数的影响。由于我们可以把从根向下查找的代价计算到伸展过程中对应的旋转操作上,此时旋转操作复杂度不变,只是常数增大,从而忽略了查找对复杂度的影响。我们可以简单地通过增大势的单位来支配隐藏在操作中的常数。因此我们只需证明对于一次伸展操作的所有旋转操作,其复杂度是均摊\(O(\log|T|)\)的,我们就完成了对\(Splay\)复杂度的证明。

\(1\)、\(zig\)操作

由于\(zag\)操作与\(zig\)相似,因此只需要证明\(zig\)即可。

假设我们\(zig\)的对象是\(x\),其父亲为\(y\),显然在旋转以后,只有\(x\)和\(y\)的子树大小发生了变化。因此势能变化量为:

\[\Delta\Phi(T)=\phi(x')+\phi(y')-\phi(x)-\phi(y)\]

显然\(\phi(x')=\phi(y)\),且\(\phi(x')\geq \phi(y')\),因此消去\(\phi(x')\)与\(\phi(y)\),并将\(\phi(y')\)替换为\(\phi(x')\),有:

\[\Delta\Phi(T)\leq \phi(x')-\phi(x)\]

因此\(zig\)操作的均摊代价为\(O(1+\phi(x')-\phi(x))\),其中\(O(1)\)代表旋转操作本身的复杂度,而在一次伸展操作中也只会有一次\(zig\)操作,因此这额外的\(O(1)\)代价不会对分析造成影响,因此我们可以只关心其中的\(O(\phi(x')-\phi(x))\)。

\(2\)、\(zig-zig\)操作

由于\(zag-zag\)操作与\(zig-zig\)相似,因此只需要证明\(zig-zig\)即可。

假设我们\(zig-zig\)的对象是\(x\),其父亲为\(y\),其祖父为\(z\),与\(zig\)操作类似,势能变化量为:

\[\Delta\Phi(T)=\phi(x')+\phi(y')+\phi(z')-\phi(x)-\phi(y)-\phi(z)\]

同样地,由于\(\phi(x')=\phi(z)\),因此将它们消去:

\[\Delta\Phi(T)=\phi(y')+\phi(z')-\phi(x)-\phi(y)\]

而我们又有\(\phi(x')\geq \phi(y')\),\(\phi(x)\leq \phi(y)\),因此有:

\[\Delta\Phi(T)\leq \phi(x')+\phi(z')-2\phi(x)\]

推到这里,我们先来做一个小工作,来证明\(\phi(x)+\phi(z')-2\phi(x')\)(注意与上面的式子不一样)的值不大于\(-1\)。

假设\(|x|=a\),\(|z'|=b\),那么我们有:

\[\phi(x)+\phi(z')-2\phi(x')=\log|x|+\log|z'|-2\log|x'|\]

我们将\(\log\)合并,得到:

\[\phi(x)+\phi(z')-2\phi(x')=\log(\frac{|x||z'|}{|x'|^2})\]

由于\(|x'|\geq a+b\)(可以结合旋转过程思考一下),而\(\log\)是单调的,因此:

\[\phi(x)+\phi(z')-2\phi(x')\leq \log(\frac{ab}{(a+b)^2})\leq \log(\frac{ab}{2ab})\leq -1\]

证明完毕。现在我们已经知道\(zig-zig\)操作的摊还代价不大于:

\[O(1)+\phi(x')+\phi(z')-2\phi(x)\]

其中\(O(1)\)为旋转操作的复杂度。由于之前的推导我们可以知道\(\phi(x)+\phi(z')-2\phi(x')\leq -1\),因此\(-1-(\phi(x)+\phi(z')-2\phi(x'))\geq 0\),我们在摊还代价上加上这个非负数得到:

\[O(1)+\phi(x')+\phi(z')-2\phi(x)-1-(\phi(x)+\phi(z')-2\phi(x'))\]

化简一下,就得到:

\[O(1)+O(\phi(x')-\phi(x))-1\]

通过增大我们刚刚加的那个非负数以及势的单位,我们就可以支配隐藏在\(O(1)\)中的常数,因此一次\(zig-zig\)操作的摊还代价为:

\[O(\phi(x')-\phi(x))\]

\(3\)、\(zig-zag\)操作

分析的过程和\(zig-zig\)操作完全一样,之前分析用到的所有性质此时仍然适用,因此略过分析过程。其摊还代价依然为:

\[O(\phi(x')-\phi(x))\]

\(4\)、总结

综上所述,除了最后一次旋转可能增加\(O(1)\)的代价以外,其余操作的摊还代价只和我们伸展的对象\(x\)的势有关。我们假设旋转操作一共执行了\(n\)次,并用\(x_i\)来表示节点\(x\)在经过\(i\)次旋转后的状态,那么整一个伸展操作的摊还代价就为:

\[O\Big(1+\sum_{i=1}^n\phi(x_i)-\phi(x_{i-1})\Big)\]

显然除了\(\phi(x_n)\)与\(\phi(x_0)\)外,所有的势都被抵消了,因此摊还代价为:

\[O(1+\phi(x_n)-\phi(x_0))\]

至此,我们不必关心\(\phi(x_0)\)的值了。此时\(x_n\)是整棵\(Splay\)的根,因此\(\phi(x_n)=\log|T|\)。我们成功的证明了一次伸展操作的摊还代价为\(O(\log|T|)\)。

伸展树(Splay)复杂度证明的更多相关文章

  1. 纸上谈兵: 伸展树 (splay tree)[转]

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!  我们讨论过,树的搜索效率与树的深度有关.二叉搜索树的深度可能为n,这种情况下,每 ...

  2. K:伸展树(splay tree)

      伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(lgN)内完成插入.查找和删除操作.在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使 ...

  3. 高级搜索树-伸展树(Splay Tree)

    目录 局部性 双层伸展 查找操作 插入操作 删除操作 性能分析 完整源码 与AVL树一样,伸展树(Splay Tree)也是平衡二叉搜索树的一致,伸展树无需时刻都严格保持整棵树的平衡,也不需要对基本的 ...

  4. 树-伸展树(Splay Tree)

    伸展树概念 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由Daniel Sleator和Robert Tarjan创造. (01) 伸展树属于二 ...

  5. [Splay伸展树]splay树入门级教程

    首先声明,本教程的对象是完全没有接触过splay的OIer,大牛请右上角.. 首先引入一下splay的概念,他的中文名是伸展树,意思差不多就是可以随意翻转的二叉树 PS:百度百科中伸展树读作:BoGa ...

  6. 伸展树(Splay tree)的基本操作与应用

    伸展树的基本操作与应用 [伸展树的基本操作] 伸展树是二叉查找树的一种改进,与二叉查找树一样,伸展树也具有有序性.即伸展树中的每一个节点 x 都满足:该节点左子树中的每一个元素都小于 x,而其右子树中 ...

  7. 【BBST 之伸展树 (Splay Tree)】

    最近“hiho一下”出了平衡树专题,这周的Splay一直出现RE,应该删除操作指针没处理好,还没找出原因. 不过其他操作运行正常,尝试用它写了一道之前用set做的平衡树的题http://codefor ...

  8. 伸展树Splay【非指针版】

    ·伸展树有以下基本操作(基于一道强大模板题:codevs维护队列): a[]读入的数组;id[]表示当前数组中的元素在树中节点的临时标号;fa[]当前节点的父节点的编号;c[][]类似于Trie,就是 ...

  9. ZOJ 3765 Lights (zju March I)伸展树Splay

    ZJU 三月月赛题,当时见这个题目没辙,没学过splay,敲了个链表TLE了,所以回来好好学了下Splay,这道题目是伸展树的第二题,对于伸展树的各项操作有了更多的理解,这题不同于上一题的用指针表示整 ...

  10. [数据结构]伸展树(Splay)

    #0.0 写在前面 Splay(伸展树)是较为重要的一种平衡树,理解起来也依旧很容易,但是细节是真的多QnQ,学一次忘一次,还是得用博客加深一下理解( #1.0 Splay! #1.1 基本构架 Sp ...

随机推荐

  1. mfc CTabCtrl

    知识点: CTabCtrl常用属性 CTabCtrl类常用成员函数 CTabCtrl代码示例 一.CTabCtrl控件属性 Bottom:底部样式 Vertical:垂直样式 与Bottom结合使用, ...

  2. PowerBI开发 第二篇:数据建模

    在分析数据时,不可能总是对单个数据表进行分析,有时需要把多个数据表导入到PowerBI中,通过多个表中的数据及其关系来执行一些复杂的数据分析任务,因此,为准确计算分析的结果,需要在数据建模中,创建数据 ...

  3. WPF DataGrid列设置为TextBox控件的相关绑定

    在wpf的DataGrid控件中,某一列的数据模板为TextBox控件的话,绑定Text="{Binding TxtSn, UpdateSourceTrigger=PropertyChang ...

  4. 阿里云centos 安装禅道

    下载 我的阿里云服务器系统是 centos6.8 64 位,下载的禅道版本是 Linux 64位一键安装包(适用于Linux 64位) 由于阿里云服务器没桌面,所以下载用不了浏览器,可考虑在本地下载后 ...

  5. C++ new和delete 堆和栈

    一.new和delete基本用法 程序开发中内存的动态分配与管理永远是一个让C++开发者头痛的问题,在C中,一般是通过malloc和free来进行内存分配和回收的,在C++中,new和delete已经 ...

  6. Linux第五章笔记

    5.1 与内核通信 系统调用在用户空间进程和硬件设备之间添加了一个中间层. 主要作用有: 为用户空间提供了一种硬件的抽象接口 系统调用保证了系统的稳定和安全 每个进程都需要运行在虚拟机内 5.2 AP ...

  7. Linux内核分析第五章读书笔记

    第五章 系统调用 在操作系统中,内核提供了用户进程与内核进行交互的一组接口,这些接口在应用程序和内核之间扮演了使者的角色,保证系统稳定可靠,避免应用程序肆意妄行. 5.1 与内核通信 系统调用在用户空 ...

  8. level3

    伸冤下:老师的评论是有看到!看完我就去修改程序了,忘记回复请原谅!= = 前阵子都在修改功能和思路,但是由于一个细节的错误找不到,导致没有成品可以上传...求谅解. 细心真的很重要 = =!!! im ...

  9. 第十二周(12.01-12.04)----final评论I

    1.  约跑App——nice!:作为final发布讲说的第一组,nice团队很不容易.虽然很早就来到了发布场地,为发布做准备.但是准备上还是有些不足.对于摄像头的不稳定,nice没有很好的解决.在演 ...

  10. Ubuntu16解锁root

    administrator@rgqancy:~$ sudo passwd -u root [sudo] administrator 的密码: 对不起,请重试. [sudo] administrator ...