伸展树的基本操作与应用

安徽省芜湖一中 杨思雨

【关键字】

伸展树 基本操作 应用

【摘要】

本文主要介绍了伸展树的基本操作以及其在解题中的应用。全文可以分为以下四个部分。

第一部分引言,主要说明了二叉查找树在信息学竞赛中的重要地位,并且指出二叉查找树在某些情况下时间复杂度较高,进而引出了在时间复杂度上更为优秀的伸展树。

第二部分介绍了伸展树的基本操作。并给出了对伸展树时间复杂度的分析和证明,指出伸展树的各种基本操作的平摊复杂度均为 O(log n),说明伸展树是一种较平衡的二叉查找树。

第三部分通过一个例子介绍了伸展树在解题中的应用,并将它与其它树状数据结构进行了对比。

第四部分指出了伸展树的优点,总结全文。

【引言】

二叉查找树(Binary Search Tree)能够支持多种动态集合操作。因此,在信息学竞赛中,二叉排序树起着非常重要的作用,它可以被用来表示有序集合、建立索引或优先队列等。

作用于二叉查找树上的基本操作的时间是与树的高度成正比的。对一个含 n 各节点的完全二叉树,这些操作的最坏情况运行时间为 O(log n)。但如果树是含 n 个节点的线性链,则这些操作的最坏情况运行时间为 O(n)。而有些二叉查找树的变形,其基本操作在最坏情况下性能依然很好,比如红黑树、AVL 树等等。

本文将要介绍的伸展树(Splay Tree),也是对二叉查找树的一种改进,虽然它并不能保证树一直是“平衡”的,但对于伸展树的一系列操作,我们可以证明其每一步操作的平摊复杂度都是 O(log n)。所以从某种意义上说,伸展树也是一种平衡的二叉查找树。而在各种树状数据结构中,伸展树的空间要求与编程复杂度也都是很优秀的。

【伸展树的基本操作】

伸展树是二叉查找树的一种改进,与二叉查找树一样,伸展树也具有有序性。即伸展树中的每一个节点 x 都满足:该节点左子树中的每一个元素都小于 x,而其右子树中的每一个元素都大于 x。与普通二叉查找树不同的是,伸展树可以自我调整,这就要依靠伸展操作 Splay(x,S)。

伸展操作 Splay(x,S)

伸展操作 Splay(x,S)是在保持伸展树有序性的前提下,通过一系列旋转将伸展树 S中的元素 x调整至树的根部。在调整的过程中,要分以下三种情况分别处理:

情况一:节点 x 的父节点 y 是根节点。这时,如果 x 是 y 的左孩子,我们进行一次 Zig(右旋)操作;如果 x 是 y 的右孩子,则我们进行一次 Zag(左旋)操作。经过旋转,x 成为二叉查找树 S 的根节点,调整结束。如图 1 所示

图 1

情况二:节点 x 的父节点 y 不是根节点,y 的父节点为 z,且 x 与 y 同时是各自父节点的左孩子或者同时是各自父节点的右孩子。这时,我们进行一次Zig-Zig 操作或者 Zag-Zag 操作。如图 2 所示

图 2

情况三:节点 x 的父节点 y 不是根节点,y 的父节点为 z,x 与 y 中一个是其父节点的左孩子而另一个是其父节点的右孩子。这时,我们进行一次 Zig-Zag 操作或者 Zag-Zig 操作。如图 3 所示

图 3

如图 4所示,执行 Splay(1,S),我们将元素 1调整到了伸展树 S的根部。再执行 Splay(2,S),如图 5所示,我们从直观上可以看出在经过调整后,伸展树比原来“平衡”了许多。而伸展操作的过程并不复杂,只需要根据情况进行旋转就可以了,而三种旋转都是由基本得左旋和右旋组成的,实现较为简单。

图 4 Splay(1,S)

图 5 Splay(2,S)

伸展树的基本操作

利用 Splay 操作,我们可以在伸展树 S上进行如下运算:

(1)Find(x,S):判断元素 x是否在伸展树 S 表示的有序集中。

首先,与在二叉查找树中的查找操作一样,在伸展树中查找元素 x。如果 x 在树中,则再执行 Splay(x,S)调整伸展树。

(2)Insert(x,S):将元素 x 插入伸展树 S 表示的有序集中。

首先,也与处理普通的二叉查找树一样,将 x 插入到伸展树 S中的相应位置上,再执行 Splay(x,S)。

(3)Delete(x,S):将元素 x从伸展树 S所表示的有序集中删除。

首先,用在二叉查找树中查找元素的方法找到 x 的位置。如果 x 没有孩子或只有一个孩子,那么直接将 x 删去,并通过 Splay 操作,将 x 节点的父节点调整到伸展树的根节点处。否则,则向下查找 x 的后继 y,用 y 替代 x 的位置,最后执行 Splay(y,S),将 y 调整为伸展树的根。

(4)Join(S1,S2):将两个伸展树 S1与 S2 合并成为一个伸展树。其中 S1的所有元素都小于 S2的所有元素。

首先,我们找到伸展树 S1中最大的一个元素 x,再通过 Splay(x,S1)将 x调整到伸展树 S1 的根。然后再将 S2 作为 x 节点的右子树。这样,就得到了新的伸展树 S。如图 6 所示

图 6

(5)Split(x,S):以 x 为界,将伸展树 S分离为两棵伸展树 S1和 S2,其中 S1中所有元素都小于 x,S2 中的所有元素都大于 x。

首先执行 Find(x,S),将元素 x调整为伸展树的根节点,则 x 的左子树就是S1,而右子树为 S2。如图 7所示

图 7

除了上面介绍的五种基本操作,伸展树还支持求最大值、求最小值、求前趋、求后继等多种操作,这些基本操作也都是建立在伸展操作的基础上的。

时间复杂度分析

由以上这些操作的实现过程可以看出,它们的时间效率完全取决于 Splay操作的时间复杂度。下面,我们就用会计方法来分析 Splay 操作的平摊复杂度。

首先,我们定义一些符号:S(x)表示以节点 x 为根的子树。|S|表示伸展树 S的节点个数。令μ(S) = [ log|S| ],μ(x)=μ(S(x))。如图 8 所示

图 8

我们用 1 元钱表示单位代价(这里我们将对于某个点访问和旋转看作一个单位时间的代价)。定义伸展树不变量:在任意时刻,伸展树中的任意节点 x 都至少有μ(x)元的存款。

在 Splay 调整过程中,费用将会用在以下两个方面:

(1)为使用的时间付费。也就是每一次单位时间的操作,我们要支付 1 元钱。

(2)当伸展树的形状调整时,我们需要加入一些钱或者重新分配原来树中每个节点的存款,以保持不变量继续成立。

下面我们给出关于 Splay 操作花费的定理:

定理:在每一次 Splay(x,S)操作中,调整树的结构与保持伸展树不变量的总花费不超过 3μ(S)+1。

证明:用μ(x)和μ’(x)分别表示在进行一次 Zig、Zig-Zig 或 Zig-Zag 操作前后节点 x处的存款。

下面我们分三种情况分析旋转操作的花费:

情况一:如图 9 所示

图 9

我们进行 Zig 或者 Zag 操作时,为了保持伸展树不变量继续成立,我们需要

花费:

μ’(x) +μ’(y) -μ(x) -μ(y) = μ’(y) -μ(x)

≤ μ’(x) -μ(x)

≤ 3(μ’(x) -μ(x))

= 3(μ(S) -μ(x))

此外我们花费另外 1 元钱用来支付访问、旋转的基本操作。因此,一次 Zig 或 Zag 操作的花费至多为 3(μ(S) -μ(x))。

情况二:如图 10 所示

图 10

我们进行 Zig-Zig 操作时,为了保持伸展树不变量,我们需要花费:

μ’(x) +μ’(y) +μ’(z) -μ(x) -μ(y) -μ(z) = μ’(y) +μ’(z) -μ(x) -μ(y)

= (μ’(y) -μ(x)) + (μ’(z) -μ(y))

≤ (μ’(x) -μ(x)) + (μ’(x) -μ(x))

= 2(μ’(x) -μ(x))

与上种情况一样,我们也需要花费另外的 1 元钱来支付单位时间的操作。

当μ’(x) <μ(x) 时,显然 2 (μ’(x) -μ(x))+1 ≤ 3 (μ’(x) -μ(x))。也就是进行 Zig-Zig 操作的花费不超过 3 (μ’(x) -μ(x))。

当μ’(x) =μ(x) 时,我们可以证明μ’(x) +μ’(y) + μ’(z) <μ(x) +μ(y) +μ(z),也就是说我们不需要任何花费保持伸展树不变量,并且可以得到退回来的钱,用其中的 1 元支付访问、旋转等操作的费用。为了证明这一点,我们假设μ’(x) +μ’(y) + μ’(z) >μ(x) +μ(y) +μ(z)。

联系图 9,我们有μ(x) =μ’(x) =μ(z)。那么,显然μ(x) =μ(y) =μ(z)。于是,可以得出μ(x) =μ’(z) =μ(z)。令 a= 1 + |A| + |B|,b= 1 + |C| + |D|,那么就有[log a] = [log b] =[log (a+b+1)]。 ①

我们不妨设 b≥a,则有[log (a+b+1)] ≥ [log (2a)]

= 1+[log a]

> [log a] ②

①与②矛盾,所以我们可以得到μ’(x) =μ(x) 时,Zig-Zig 操作不需要任何花费,显然也不超过 3 (μ’(x) -μ(x))。

情况三:与情况二类似,我们可以证明,每次 Zig-Zag 操作的花费也不超过3 (μ’(x) -μ(x))。

以上三种情况说明,Zig 操作花费最多为 3(μ(S)-μ(x))+1,Zig-Zig 或 Zig-Zag操作最多花费 3(μ’(x)-μ(x))。那么将旋转操作的花费依次累加,则一次 Splay(x,S)操作的费用就不会超过 3μ(S)+1。也就是说对于伸展树的各种以 Splay操作为基础的基本操作的平摊复杂度,都是 O(log n)。所以说,伸展树是一种时间效率非常优秀的数据结构。

【伸展树的应用】

伸展树作为一种时间效率很高、空间要求不大的数据结构,在解题中有很大的用武之地。下面就通过一个例子说明伸展树在解题中的应用。

例:营业额统计 Tur nover ( 湖南省队 2002 年选拔赛)

题目大意

Tiger 最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。Tiger 拿出了公司的账本,账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。经济管理学上定义了一种最小波动值来衡量这种情况:

该天的最小波动值= min { | 该天以前某一天的营业额-该天的营业额 | }

当最小波动值越大时,就说明营业情况越不稳定。而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。你的任务就是编写一个程序帮助 Tiger 来计算这一个值。

注:第一天的最小波动值为第一天的营业额。

数据范围:天数 n≤32767,每天的营业额 ai≤1,000,000。最后结果 T≤231

初步分析

题目的意思非常明确,关键是要每次读入一个数,并且在前面输入的数中找到一个与该数相差最小的一个。

我们很容易想到 O(n2)的算法:每次读入一个数,再将前面输入的数一次查找一遍,求出与当前数的最小差值,记入总结果 T。但由于本题中 n 很大,这样的算法是不可能在时限内出解的。而如果使用线段树记录已经读入的数,就需要记下一个 2M 的大数组,这在当时比赛使用 TurboPascal 7.0 编程的情况下是不可能实现的。而前文提到的红黑树与平衡二叉树虽然在时间效率、空间复杂度上都比较优秀,但过高的编程复杂度却让人望而却步。于是我们想到了伸展树算法。

算法描述

进一步分析本题,解题中,涉及到对于有序集的三种操作:插入、求前趋、求后继。而对于这三种操作,伸展树的时间复杂度都非常优秀,于是我们设计了如下算法:

开始时,树 S 为空,总和 T 为零。每次读入一个数 p,执行 Insert(p,S),将 p 插入伸展树 S。这时,p 也被调整到伸展树的根节点。这时,求出 p 点左子树中的最右点和右子树中的最左点,这两个点分别是有序集中 p 的前趋和后继。然后求得最小差值,加入最后结果 T。

解题小结

由于对于伸展树的基本操作的平摊复杂度都是 O(log n)的,所以整个算法的时间复杂度是 O(nlog n),可以在时限内出解。而空间上,可以用数组模拟指针存储树状结构,这样所用内存不超过 400K,在 TP 中使用动态内存就可以了。

编程复杂度方面,伸展树算法非常简单,程序并不复杂。虽然伸展树算法并不是本题唯一的算法,但它与其他常用的数据结构相比还是有很多优势的。下面的表格就反映了在解决这一题时各个算法的复杂度。从中可以看出伸展树在各方面都是优秀的,这样的算法很适合在竞赛中使用。

【总结】

由上面的分析介绍,我们可以发现伸展树有以下几个优点:

(1)时间复杂度低,伸展树的各种基本操作的平摊复杂度都是 O(log n)的。在树状数据结构中,无疑是非常优秀的。

(2)空间要求不高。与红黑树需要记录每个节点的颜色、AVL 树需要记录平衡因子不同,伸展树不需要记录任何信息以保持树的平衡。

(3)算法简单,编程容易。伸展树的基本操作都是以 Splay 操作为基础的,而Splay 操作中只需根据当前节点的位置进行旋转操作即可。虽然伸展树算法与 AVL 树在时间复杂度上相差不多,甚至有时候会比 AVL树慢一些,但伸展树的编程复杂度大大低于 AVL 树。在竞赛中,使用伸展树在编程和调试中都更有优势。

在信息学竞赛中,不能只一味的追求算法有很高的时间效率,而需要在时间复杂度、空间复杂度、编程复杂度三者之间找到一个“平衡点”,合理的选择算法。这也需要我们在平时对各种算法反复琢磨,深入研究,在竞赛中才能够游刃有余的应用。

【参考书目】

[1] 傅清祥,王晓东. 《算法与数据结构》. 电子工业出版社. 1998. 01

[2] 严蔚敏,吴伟民. 《数据结构( 第二版) 》. 清华大学出版社. 1992. 06

[3] 《Introduction to Algorithms,Second Edition》. 2001

源地址:下载

伸展树的基本操作与应用 IOI2004 国家集训队论文 杨思雨相关推荐

  1. 国家集训队论文分类整理[转]

    国家集训队论文分类整理 转自这里 dalao写的东西,是非常有学习价值的.反正noip不一定会用,但是对以后肯定有用的. 组合数学 计数与统计 2001 - 符文杰:<Pólya原理及其应用&g ...

  2. 国家集训队论文分类整理

    国家集训队论文分类整理 组合数学 计数与统计 2001 - 符文杰:<Pólya原理及其应用> 2003 - 许智磊:<浅谈补集转化思想在统计问题中的应用> 2007 - 周冬 ...

  3. 国家集训队论文分类整理(转)

    国家集训队论文分类整理 ----------转自https://www.cnblogs.com/AbandonZHANG/archive/2012/07/21/2601889.html 距离ACM/I ...

  4. acm国家集训队论文(1999-2009)

    原文地址http://blog.sina.com.cn/s/blog_49eda3f30100r6ip.html 国家集训队1999论文集  陈宏:<数据结构的选择与算法效率--从IOI98试题 ...

  5. 【转载】国家集训队论文分类

    国家集训队论文分类 组合数学 计数与统计 2001 - 符文杰:<Pólya原理及其应用> 2003 - 许智磊:<浅谈补集转化思想在统计问题中的应用> 2007 - 周冬:& ...

  6. 【转】国家集训队论文分类

    呵呵- -,今天觉得看论文是不错的注意,于是就这样做了.先是整理. 转载地  : 点击打开链接 组合数学 计数与统计 2001 – 符文杰:<Pólya原理及其应用> 2003 – 许智磊 ...

  7. noi国家集训队论文分类

    组合数学 计数与统计 2001 - 符文杰:<Pólya原理及其应用> 2003 - 许智磊:<浅谈补集转化思想在统计问题中的应用> 2007 - 周冬:<生成树的计数及 ...

  8. POJ 3580 SuperMemo(伸展树的基本操作)

    题目大意:给你六个操作,让你实现这些功能. 解题思路:伸展树的基本应用,用伸展数实现各种功能. SuperMemo Time Limit: 5000MS   Memory Limit: 65536K ...

  9. 伸展树算法c语言,数据结构之伸展树详解

    1. 概述 二叉查找树(Binary Search Tree,也叫二叉排序树,即Binary Sort Tree)能够支持多种动态集合操作,它可以用来表示有序集合.建立索引等,因而在实际应用中,二叉排 ...

最新文章

  1. 浅谈Javascript中的void操作符
  2. 病毒与木马大多作成 动态库形式的原因
  3. Logstash入门简介
  4. html层次选择器例题,详解强大的jQuery选择器之基本选择器、层次选择器
  5. Tensorflow学习笔记4:分布式Tensorflow
  6. gfdmp和mysql,《高性能MySQL》读书笔记--锁、事务、隔离级别
  7. 数据科学在风控中的应用-笔记
  8. 【语音处理】基于matlab GUI录音信号时域频域分析(带面板)【含Matlab源码 064期】
  9. 计算机常用算法对照表整理
  10. 三相桥式全控整流电路simulink仿真_维修电工实训仿真软件-电工入门与提高
  11. godaddy购买域名
  12. Linux驱动(并发):02---编译乱序、执行乱序(屏障API(bm、rmb、wmb、__iormb、__iowmb))
  13. 记录---第一次写博客
  14. ubuntu 18.04 卸载firebox
  15. STM32外部中断总结
  16. FPGA实现花样彩灯
  17. 苹果手机自带软件删除了怎么恢复_手机照片删除了怎么恢复?莫慌,这才是正确恢复方法!_...
  18. pure virtual method called 纯虚函数被调用原理分析
  19. 计算机故障处理试题,模块五计算机故障诊断与排除 测试题
  20. js:三种简单的矩形绘制方法(画布canvas)

热门文章

  1. vue本地存储案例_本地化还是创意适应? 流氓游戏街的案例研究
  2. 利用Java实现:将字符串“hello1234”改为“HELLO2345
  3. 读书笔记:《金融的逻辑》——陈志武
  4. 2016/10/08 TCL技术笔试题目
  5. 银行测试(8)-支付测试用例
  6. 股票自动交易软件接口支持的语言是什么?
  7. layout_constraintWidth_percent in java
  8. reading : Mask R-CNN(Kaiming He Georgia Gkioxari Piotr Dolla ́r Ross Girshick Facebook AI Research)
  9. ros中关于ros::Rate 和ros::spin()等的理解
  10. Lombok 注解说明