简述

  Splay树是一种二叉查找平衡树,其又名伸展树,缘由是对其进行任意操作,树的内部结构都会发生类似伸张的动作,换言之,其读和写操作都会修改树的结构。Splay树拥有和其它二叉查找平衡树一致的读写时间复杂度O(log2(n))。Splay树的优点是实现简单(苦于红黑树的小伙伴有福了),并且功能异常强大。其缺点其一是所有操作都会修改树结构,因此对其进行的任意操作都需要进行同步,当然单线程就无需进行担心。其二是Splay树的时间复杂度的常数较大。

Splay实现

splay操作

  Splay树的结点之间的关系与一般二叉树相同,其任意结点左孩子的关键字不会超过当前结点,其右孩子的关键字不会小于当前结点,因此按中序遍历得到的结点序列的关键字是递增的。Splay树的核心操作是splay(x),其将结点x通过旋转移动到树的顶端。一般二叉树的旋转大家估计都见过 ,但是splay的旋转略有不同,其是通过双旋使x的深度不断降低,下面给出splay旋转的规则(总共三个):

  1.若x的父亲y是根结点。若x是y的左孩子,则进行下面的zig旋转(若x是y的右孩子,则进行镜像操作zag,即将所有左变为右,右变为左)

  

  2.若x的父亲y不是根结点,记z为y的父亲。若x是y的左结点且y是z的左孩子,则进行下面的zigzig操作(若对称的x是y的右孩子且y是z的右孩子,则进行镜像操作zagzag)。

  

  3.若x的父亲y不是根结点,记z为y的父亲。若x是y的右结点且y是z的左结点,则进行下面的zagzig操作(若对称的x是y的左孩子,y是z的右孩子,则进行镜像操作zigzag)。

  我们在splay的过程中不断探测x此时的状况,并选择调用zig,zag,zigzig,zagzag,zigzag,zagzig六种操作中的一种,通过旋转使得x不断上升(x的深度降低),直到x成为树根。

  实际上我们不需要完全为splay实现6种旋转方案,只需要实现zig和zag即可。zigzag(x)与先后调用zig(x),zag(x)的结果一致,而zagzig则与先后调用zag(x),zig(x)的结果一致。但是要小心zigzig(x),其结果与调用两次zig(x)是不同的,应该是先调用zig(x.f)后调用zig(x),这里x.f表示x的父亲,而zazag也类似,等价于先调用zag(x.f),再调用zag(x)。这部分的说明请自行验证。

  splay操作并不会影响对原树和新树进行中序遍历得到的结果,即对于一对原树中结点x,y,若x处于y的左子树中,则在新树中或者x处于y的左子树中,或者y处于x的右子树中。这都是来源于旋转的直接性质。

插入

  下面说明插入insert(k)的具体流程,要插入关键字k,我们首先需要找到合适的插入位置,之后新建结点x并插入,之后对结点x进行splay操作。

连接

  连接join(x,y)用于将以x为根和以y为根的两株splay树连接为一株树,其中x树中所有结点的关键字都不大于y树中的所有结点的关键字。如果x或y为空树,则返回另外一株树即可。否则在y中查找关键字最小的结点s,并对其调用splay操作。之后将x树作为s的左孩子进行连接。

分裂

  split(k),将树分裂为两株子树x与y,其中x中所有结点的关键字均小于k,而y中所有结点的关键字均大于等于k。我们先向树中插入一个关键字为k的结点x(但是插入过程中我们需要保证若某个树中结点的关键字为k,则x一定插入到该结点的左子树中),之后对x进行splay手续。此时树根为x,x的左孩子为小于k的树,右孩子则为其余结点组合成的树,移除x并返回其左右子树。

删除

  删除操作delete(k),删除关键字为k的任意一个结点。我们先找到某个关键字为k的结点x,若不存在,则对访问到的最深的结点f执行splay手续。否则对x执行splay操作。之后我们移除x,并将其左右子树作为两株新树,并利用连接操作进行连接。

查找

  find(k),查找关键字为k的任意一个结点。由于Splay树的存储是有序的,因此不断地根据子树根结点的关键字与k的关系,选择继续搜索其左子树还是右子树,或者根结点的关键字为k,这个流程与在一般二叉查找树中寻找指定关键字的步骤和流程完全相同。如果找到,则返回该结点,否则返回空。不管是否找到,在离开前,都不要忘了为查找过的最深的顶点f执行splay手续。

时间复杂度

  很容易发现每次操作的时间复杂度与该次操作执行的splay操作的时间复杂度+O(1)是一致的,其中+O(1)是由于存在常数时间的费用。

  我们记第i次操作前数据结构的势能为Di-1,而第i次操作后数据结构的势能为Di,之后定义s(x)表示以x为根结点的子树中结点总数,记d(x)=log2(s(x)),同样定义s(T)为树T中的结点总数。而我们将Splay树的势能定义为D=∑d(x),其中x取树中的所有不同结点。记ci表示Splay操作中第i次上升x所付出的实际时间费用,我们认为每次操作的时间费用ci为1,即将其作为单位费用,记ci+Di-Di-1为第i次上升的摊还费用。很显然D0=0,这也是势能的下界,因此我们可以保证∑(ci+Di-Di-1)=∑ci+Dn-D0是该次操作时间复杂度的一个上界。

  对于一次对x的上升,其可能对应六种模式,zig,zag,zigzig,zagzag,zigzag,zagzig。不考虑镜像模式(镜像模式只是修改了左右,因此时间复杂度与原来的模式一致),我们需要分析zig,zigzig,zigzag的摊还费用。

  对于zig操作,观察对应的图,我们可以得出下面公式的成立(结点名称后面加'表示变换后的结点):

$$ d\left(x'\right)+d\left(y'\right)-d\left(x\right)-d\left(y\right)=d\left(y'\right)-d\left(x\right)\le d\left(x'\right)-d\left(x\right)\le 3d\left(x'\right)-3d\left(x\right) $$

  而对于zigzig操作,观察对应的图,得出:

$$ d\left(x'\right)+d\left(y'\right)+d\left(z'\right)-d\left(x\right)-d\left(y\right)-d\left(z\right)=d\left(y'\right)+d\left(z'\right)-d\left(x\right)-d\left(y\right) $$

且由于

$$ 2d\left(x'\right)-d\left(z'\right)-d\left(x\right)=\log_2\left(\frac{\left[s\left(x'\right)\right]^2}{s\left(z'\right)s\left(x\right)}\right)\geqslant\log_2\left(4\right)=2 $$

从而得到

$$ d\left(y'\right)+d\left(z'\right)-d\left(x\right)-d\left(y\right)\le d\left(x'\right)+d\left(z'\right)-2d\left(x\right) $$ $$ \le d\left(x'\right)+d\left(z'\right)-2d\left(x\right)+2d\left(x'\right)-d\left(z'\right)-d\left(x\right)-2=3d\left(x'\right)-3d\left(x\right)-2 $$

  再考虑zigzag操作,观察对应的图,得出:

$$ d\left(x'\right)+d\left(y'\right)+d\left(z'\right)-d\left(x\right)-d\left(y\right)-d\left(z\right)=d\left(y'\right)+d\left(z'\right)-d\left(x\right)-d\left(y\right) $$

且同样的有

$$ 2d\left(x'\right)-d\left(y'\right)-d\left(z'\right)\geqslant 2 $$

从而得到

$$ d\left(y'\right)+d\left(z'\right)-d\left(x\right)-d\left(y\right)\le d\left(y'\right)+d\left(z'\right)-2d\left(x\right) $$ $$ \le d\left(y'\right)+d\left(z'\right)-2d\left(x\right)+2d\left(x'\right)-d\left(y'\right)-d\left(z'\right)-2 $$ $$ =2d\left(x'\right)-2d\left(x\right)-2\le 3d\left(x'\right)-3d\left(x\right)-2 $$

  可以得出zig的摊还费用上界为3d(x')-3d(x)+1,而zigzig和zigzag的摊还费用上界为3d(x')-3d(x)。由此我们可以计算出一次splay操作的摊还时间复杂度上界为(我们将开始时的x记为x0,而第i次上升后的x记为xi,设t为总共上升次数):

$$ \sum_{i=1}^t{\left(3d\left(x_i\right)-3d\left(x_{i-1}\right)\right)}+O\left(1\right)+1=3d\left(T\right)-3d\left(x_0\right)+O\left(1\right)\le 3d\left(T\right)+O\left(1\right)=O\left(\log_2\left(|T|\right)\right) $$

因此我们到此已经证明了splay操作的摊还时间复杂度上界O(log2(|T|)),也间接地证明了所有splay操作的摊还时间复杂度上界均为O(log2(|T|))。

Splay树分析的更多相关文章

  1. Splay树-Codevs 1296 营业额统计

    Codevs 1296 营业额统计 题目描述 Description Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger拿出了公司 ...

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

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

  3. splay树入门(带3个例题)

    splay树入门(带3个例题) 首先声明,本教程的对象是完全没有接触过splay的OIer,大牛请右上角.. PS:若代码有误,请尽快与本人联系,我会尽快改正 首先引入一下splay的概念,他的中文名 ...

  4. AVL树、splay树(伸展树)和红黑树比较

    AVL树.splay树(伸展树)和红黑树比较 一.AVL树: 优点:查找.插入和删除,最坏复杂度均为O(logN).实现操作简单 如过是随机插入或者删除,其理论上可以得到O(logN)的复杂度,但是实 ...

  5. ZOJ3765 Lights Splay树

    非常裸的一棵Splay树,需要询问的是区间gcd,但是区间上每个数分成了两种状态,做的时候分别存在val[2]的数组里就好.区间gcd的时候基本上不支持区间的操作了吧..不然你一个区间里加一个数gcd ...

  6. Splay树再学习

    队友最近可能在学Splay,然后让我敲下HDU1754的题,其实是很裸的一个线段树,不过用下Splay也无妨,他说他双旋超时,单旋过了,所以我就敲来看下.但是之前写的那个Splay越发的觉得不能看,所 ...

  7. 暑假学习日记:Splay树

    从昨天开始我就想学这个伸展树了,今天花了一个上午2个多小时加下午2个多小时,学习了一下伸展树(Splay树),学习的时候主要是看别人博客啦~发现下面这个博客挺不错的http://zakir.is-pr ...

  8. 1439. Battle with You-Know-Who(splay树)

    1439 路漫漫其修远兮~ 手抄一枚splay树 长长的模版.. 关于spaly树的讲解   网上很多随手贴一篇 貌似这题可以用什么bst啦 堆啦 平衡树啦 等等 这些本质都是有共同点的 查找.删除特 ...

  9. 伸展树(Splay树)的简要操作

    伸展树(splay树),是二叉排序树的一种.[两个月之前写过,今天突然想写个博客...] 伸展树和一般的二叉排序树不同的是,在每次执行完插入.查询.删除等操作后,都会自动平衡这棵树.(说是自动,也就是 ...

随机推荐

  1. 常用的两个PHP类

      /** * Class Interval * @author logonmy * @desc 简单分析程序执行时间: */   Class Interval{ var $start;   publ ...

  2. Linux系统中的wc

    Linux系统中的wc(Word Count)命令的功能为统计指定文件中的字节数.字数.行数,并将统计结果显示输出. 1.命令格式: wc [选项]文件... 2.命令功能: 统计指定文件中的字节数. ...

  3. struts2逻辑视图类型汇总与解释(转)

    在struts2框架中,当action处理完之后,就应该向用户返回结果信息,该任务被分为两部分:结果类型和结果本身. 结果类型提供了返回给用户信息类型的实现细节.结果类型通常在Struts2中就已预定 ...

  4. 【解题报告】2014ACM/ICPC上海赛区现场赛B

    唉  谷歌出的神题,差点爆零了...三小时终于A掉 B题 题目大概是说从左上角的点出发,经过某路线最后回到原点,求每个格子被路线包含的圈数的平方和. 首先可以知道,对于某个格子来说,从该格子的任意一个 ...

  5. spring的笔记1 关云长

    1.1   实例化方式 l  3种bean实例化方式:默认构造.静态工厂.实例工厂 1.1.1  默认构造 <bean id="" class=""> ...

  6. 组件与.NET互操作

    组件 1.何谓组件技术? 组件技术就是利用某种编程手段,将一些人们所关心的,但又不便于让最终用户去直接操作的细节进行了封装,同时对各种业务逻辑规则进行了实现,用于处理用户的内部操作细节,甚至于将安全机 ...

  7. matlab的fda工具使用方法

    MATLAB中用FDATool设计滤波器及使用 该文章讲述了MATLAB中用FDATool设计滤波器及使用. 1. 在Matlab中键入fdatool运行Filter Design and Analy ...

  8. 洛谷 P2626 斐波那契数列(升级版)

    题目背景 大家都知道,斐波那契数列是满足如下性质的一个数列: • f(1) = 1 • f(2) = 1 • f(n) = f(n-1) + f(n-2) (n ≥ 2 且 n 为整数). 题目描述 ...

  9. git自用笔记

    同步远程库:git clone xxx.git [filename] git ls-files: 查看已经添加进暂存区的文件. 在commit前修改一个文件后(假设名为:xxx.file),想撤销时, ...

  10. Android 比对APK的签名信息

    https://www.jianshu.com/p/8583f6a966e2 在做App的时候经常会有验证apk是否为正版的需求,比如一些接入第三方支付的app,接入微信sdk也是需要apk签名信息的 ...