伸展树

Posted caijiaming

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了伸展树相关的知识,希望对你有一定的参考价值。

  我们知道,二叉查找树能够支持多种动态集合操作,因此在程序设计竞赛中,二叉查找树起着非常重要的作用,它可以用来表示有序集合,建立索引或优先队列等。作用于二叉树的基本操作时间是与树的高度成正比的:对于一颗含n个节点的二叉查找树,如果呈完全二叉树结构,则这些操作的最坏情况的运行时间为O(log2 n); 但如果呈线性链结构,则这些操作的最坏情况运行时间退化为O(n)。 针对二叉树这种不平衡、不稳定的弊病,人们做了大量改进优化的尝试,使其基本操作在最坏情况下的性能尽量保持良好。本节将介绍两种改进型的二叉查找树:

  1、伸展树(splay tree):

  这里要介绍的伸展树是一种改进型的二叉查找树。 虽然它并不能完全保证树结构始终平衡,但对于伸展树的一系列操作,我们可以证明其每一步操作的平摊复杂度都是O(log2 n)。所以从某种意义上来说,这种改进厚的二叉查找树能够基本达到一种平衡状态。至于伸展树的空间要求与编程复杂度,在各种树状数据结构中都是很优秀的。

  伸展树的基本操作包括:

  1、伸展操作,即伸展树作自我调整。

  2、判断元素x是否在伸展树中。

  3、将元素x插入到伸展树。

  4、将元素x从伸展树中删除。

  5、将两颗伸展树S1和S2合并为一颗伸展树(其中S1的所有元素都小于S2的元素)。

  6、以x为界,将伸展树S分离成两颗伸展树S1和S2,其中S1中所有元素都小于x,S2中所有元素都大于x。

  其中基本操作2~6都是在伸展操作的基础上进行的。

  1、伸展操作------Splay(x,S)

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

  情况1:节点x的父节点y是根节点。

  这时,如果x是y的左儿子,我们进行一次Zig(右旋)操作;如果x是y的右儿子,则进行一次Zag(左旋)操作。经过旋转,x成为二叉树S的根节点,调整结束。我们称这种旋转为单旋转。如下图:

技术分享图片

  可以仔细看一下这个图:

  左图到右图:Zig(右旋)就是将x变为根,y变为x的右儿子,x的右儿子变为y的左儿子,这样就保证相对有序了,左儿子都小于本身,本身又小于右儿子。

  右图到左图:Zag(左旋)就是将y变为根,x变为y的左儿子,y的左儿子变为x的右儿子,这样就保证相对有序了。

  情况2:节点x的父节点y不是根节点,y的父节点为z且x与y同时是各自父节点的左孩子或者同时是各自父节点的右儿子。

  这时进行一次Zig-Zig操作或者Zag-Zag操作,即假设当前节点为x,x的父节点为y,y的父节点为z,如果y和x同为其父亲的左孩子或者右孩子,那么先旋转y,再旋转x。我们称这种旋转为一字旋转,如下图:

技术分享图片

  这里是怎么旋转的呢?  先旋转y,这时就变成了

  技术分享图片

再旋转x就变成了:

技术分享图片

  情况3:节点x的父节点y不是根节点,y的父节点为z,x与y中一个是其父节点的左儿子,另一个是其父节点的右儿子。

  这是进行一次Zig-Zag操作或者进行一次Zag-Zig操作,即连续旋转两次X。我们称这种旋转为之字型旋转。如下图所示:

技术分享图片

  如下图所示,执行Splay(1,S),我们将元素1调整到了伸展树S的根部

技术分享图片

  再执行Splay(2,S),如下图所示

技术分享图片

  我们从直观上可以看出经调整厚,伸展树比原来“平衡”了许多。伸展操作的过程并不复杂,只需要根据情况进行旋转就可以了,而三种旋转都是由基本的左旋转和右旋组成,实现较为简单。

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

  2、判断元素x是否在伸展树S表示的有序集中-------Find(x,S)

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

  3、将元素插入伸展树S表示的有序集中------Insert(x,S)

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

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

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

  5、将两颗伸展树S1和S2合并成为一颗伸展树------Join(S1,S2)(其中S1的所有元素值都小于S2的所有元素值)

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

技术分享图片

  6、以x为界,将伸展树S分离为两颗伸展树S1和S2------Split(x,S)(其中S1中所有元素都小于x,S2中所有元素都大于x)

  首先执行Find(x,S),将元素x调整为伸展树的根节点,则x的左子树就是S1,二右子树就是S2。然后去除x通往左右儿子的边,如下图所示:

技术分享图片

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

  由上述操作的实现过程可以看出,伸展树基本操作的时间效率完全取决于Splay操作的时间复杂度。下面,

 

以上是关于伸展树的主要内容,如果未能解决你的问题,请参考以下文章

伸展树(Splay Tree)

深度解析伸展树

伸展树详解及实现

Splay Tree(伸展树)

伸展树(splay tree)

伸展树