伸展树(Splay Tree)

伸展树(Splay Tree),又称分裂树,是一种在AVL树基础上进一步放松平衡条件的二叉排序树,它能够在O(log n)时间复杂度内完成插入、查找和删除操作。伸展树上的一般操作都是基于伸展操作的,还可以说伸展树是一种自调整形式的二叉查找树,他会沿着从某个节点到树根之间的路径,将当前访问的节点通过一系列的旋转操作迁移到树根。很重要的一点是:伸展树不需要维护平衡因子。它由丹尼尔·斯立特Daniel Sleator和罗伯特·恩卓·塔扬Robert Endre Tarjan在1985年发明。

动机

引入伸展树的最初动机:通过使用数据访问的局部性原理,提高数据的访问效率

局部性(Locatity): 指刚刚被访问过的数据,极有可能很快的被再次访问,这是信息处理过程中的常见现象。

引申到BST(Binary Search Tree):在BST中,刚刚被访问过的节点,极有可能很快的被再次访问或者是下一个即将被访问的节点很有很能就在刚刚被访问过节点的附近。

局部性原理下的列表与树

首先看一下局部性原理在线性结构中的表现。如下图一所示,列表结构,元素间通过引用确立前驱和后继关系。在上述列表中对任何一个元素的访问效率取决于元素在序列中的秩。秩越小,访问效率越高。
如果对于列表中元素的访问是随机的,那么平均访问效率都无法提高。但是,如果对该列表集合中元素的访问具有局部性或极强的局部性,就可以通过简明的策略提高对集合的访问效率。简单来说,一旦某个元素被访问,立刻将其移动到列表的头部。执行这样一个策略,即便最初列表中的元素是随机分布的,但是在经过一段时间的使用过后,被某段时间内被集中访问的元素,都会集中到列表的最前端区域,该区域中元素的访问效率很高。

伸展树(Splay Tree)_第1张图片

类似的性质,BST中的元素也是由引用确立节点间的前驱后继的,位于树根的元素相较于位于树叶的元素访问效率要高。如下图二所示,相较于列表,可以将BST放倒。这样我们可以发现二者可以采用相同的策略进行优化。我们可以借助局部性原理,采用与列表相同的策略,将某一段时间内经常要访问的元素通过某种方式,尽可能移送到更加接近树根的位置(也就是降低节点的深度)。

伸展树(Splay Tree)_第2张图片

策略:逐层伸展和双层伸展

所谓逐层伸展和双层伸展策略本质就是:节点V一旦被访问,其随即被转移至树根位置。

逐层伸展

对于逐层伸展,我们可以借助的最直接的方式便是AVL树中的旋转,通过旋转可以将节点的高度提升。也即是如下图三中的两种情况:
伸展树(Splay Tree)_第3张图片
进行一次有效的左旋转(zag)或右旋转(zig),均会使得在右孩子或左孩子的节点V上升一层。如果节点v, 在当前层为左孩子,对其父亲节点p进行一次zig旋转,就能够使V的高度与其父亲P互换;镜像的,如果节点v,在当前层为右孩子,对其父亲p进行一次zag旋转,就能使v的高度与其父亲p互换。

因此,我们可以通过以上两种操作在BST中执行策略,使得当前访问节点被转移至树根。其实现方式为:一旦节点v被访问,即刻执行自下而上逐层单旋(也就是执行zig(v->parent)或zag(v->parent)操作),直至v最终被转移到树根位置。

逐层伸展的性能讨论

如图五所示,最坏情况下访问节点时的执行情况

伸展树(Splay Tree)_第4张图片

如图所示,整个树中共有五个节点,并且呈线性排列,假设每一次都恰好访问到最坏的节点,结果导致一个周期以后BST再一次回到了原树的结构。在访问节点1时执行4次基本操作,访问节点2时为3 , ……,依次类推,我们可以得出结论:拥有n个节点的最坏情况下,访问一个周期的累积时间应为,进行平摊分析后时间复杂度为。因此,我们可以看出在最坏情况下,逐层伸展的旋转次数呈现周期性的算数级数演变,每个周期累积时间复杂度为,平摊时间复杂度为 。 这并不符合我们对于时间的考量,在理论上,平摊结果的时间复杂度是可以达到 的。好在,这并不是我们理论上的缺陷,而是实际操作过程中出现了问题,下面的双层伸展利用了一种巧妙的方式解决了操作中出现的问题,利用局部的改变,直接影响到了全局的时间复杂度变化。


双层伸展

作为逐层伸展的改进版,双层伸展的执行策略并没有发生变化,而是在伸展方式上做了些许改进,而这个改进在全局上造成的结果则是根本性的。

在逐层伸展过程中,我们仅仅关注当前节点v和其父节点p,因此每次旋转操作v的高度加1。在双层伸展过程中则是向上追溯两层,反复考察从v节点向上的祖孙三代 v, p = parent(v), g = parent(p) 。根据其相对位置,经过两次旋转使得v上升2层,成为(子)树根。在双层伸展的过程中,我们所能够依靠的方式仍旧是单次旋转的组合。因此也就可能出现以下几种情况:zag-zag旋转, zag-zig旋转, zig-zig旋转, zig-zag旋转。如下图五所示,为双向伸展中异侧情形。

伸展树(Splay Tree)_第5张图片

下图六所示,为双层伸展同侧情形:

伸展树(Splay Tree)_第6张图片

在AVL树中,我们知道,AVL树的调整分别四种情况LL型,LR型,RR型和RL型,分别执行zag-zag, zag-zig, zig-zig, zig-zag旋转解决相应的局部调整问题。对比AVL树的四种图形,我们可以发现双向伸展节点异侧情形与AVL树中的LR,RL情形的调整方式相同;而在双向伸展节点同侧情形与AVL树中RR,LL调整方式由略微差异。也正是这种局部调整上的差异,造成了全部的根本性变化。如下图所示,在最坏情况下,双层伸展执行完毕后造成的整棵树的结构型性差异。

伸展树(Splay Tree)_第7张图片

上图中,每次都做最坏的访问,也就是恶意访问深度最深的节点,由于局部调整的作用,使得整棵数的拓扑结构树高减半,以至于访问树中节点是不至于每次都访问到最坏的节点。按照七其折叠效果而言,一旦访问到坏节点,坏节点的对应路径长度随即减半,因此最坏情况,每次都访问到坏节点,以至于在最坏情况下,整棵树的访问时间复杂度为, 其中m 为坏节点个数,n为棵树节点个数。那么单趟伸展操作的时间复杂度为.


有代码实现请看自顶向下伸展树——伸展树续

你可能感兴趣的:(数据结构)