1.伸展
Tree,中文叫伸展树,或者分裂树。伸展树(Splay Tree)是一种二叉排序树,它能 Splay
在O(log n)内完成插入、查和删除操作。它的优势在于不需要记录用于平衡树的冗余信息。伸展树由Daniel Sleator和Robert Tarjan创造。
2.为什么会有伸展树?
假设想要对一个二叉查树执行一系列的查操作,为了使整个查时间更小,被查频率高的那些节点就应当经常处于靠近树根的位置。于是想到设计一个简单方法,在每次查节点之后对树进行重构,把被查的节点搬移到树根,这种自调整形式的二叉查树就是splay tree(伸展树),它会沿着从某个被访问节点到树根之间的路径,通过一系列的旋转把这个被访问节点搬移到树根去。
3.怎样旋转搬移至树根?
伸展树通过一系列的旋转把当前被访问节点搬移到树根,以便下次再次访问该节点时速度极快(直接访问根节点就被命中)。为了将当前被访问节点搬移到树根,我们需要沿着查路径做自底向上的旋转,直至该节点成为树根为止(伸展树的旋转是成对进行的,伸展操作不单是把当前被访问节点搬移到树根,而且还把查路径上的每个节点的深度都大致减掉一半。)。(假设当前被访问节点为X,X的父亲
节点为P(如果X的父亲节点存在),X的祖父节点为G(如果X的祖父节点存在))
每一旋转步骤都是下列三种情况之一:
第一种情况:如果P是树根,则旋转连接X和P的边(这种情况是最后一步)。
(如果X是左儿子就右旋,如果X是右儿子就左旋)
第二种情况:如果P不是树根,而且X和P本身都是左孩子或者都是右孩子,则先旋转连接P和G的边,然后再旋转连接X和P的边。
那些年我们一起追的女孩主题曲第三种情况:如果P不是树根,而且X是左孩子,P是右孩子,或者相反,则先旋转连接X 和P的边,再旋转连接X和新的P的边。王承渲是哪里人
图一
kiss kiss kiss
4.伸展树的自底向上伸展(bottom-up splay)伪码
假设在当前伸展树中的X节点处进行伸展,X的父亲节点为P(X)(如果X的父亲节点存在),X的祖父节点为G(X)(如果X的祖父节点存在)。
FUNC bottom-up-splay
DO
IF X是P(X)的左孩子节点 THEN
IF G(X)为空 THEN
右旋P(X)
P(X)是G(X)的左孩子节点 THEN
ELSEIF
右旋G(X)
右旋P(X)
ELSE
右旋P(X)
左旋P(X)(注意:经过上一次右旋后此处的P(X)和上一个P(X)不一样)ENDIF
ELSE
X是P(X)的右孩子节点 THEN
IF G(X)为空 THEN
左旋P(X)
P(X)是G(X)的右孩子节点 THEN
ELSEIF
左旋G(X)
左旋P(X)
ELSE奢望歌词
左旋P(X)
右旋P(X) (注意:经过上一次左旋后此处的P(X)和上一个P(X)不一样)ENDIF
ENDIF
P(X)不为空
WHILE
ENDFUNC
仔细分析各种情况下的旋转,对于X、P、G成z字型排列的旋转情况(上面所说的三种情况中的第三种),其第二次旋转可以延后进行,这样就可以使得第三种情况和第一种情况统一起来而简化编程,从而得到简化后的伪代码,结果如下所示:
FUNC bottom-up-splay
DO
IF X是P(X)的左孩子节点 THEN
IF P(X)是G(X)的左孩子节点 THEN
右旋G(X)
ENDIF
右旋P(X)
X是P(X)的右孩子节点 THEN
网络情缘侃侃ELSE
IF P(X)是G(X)的右孩子节点 THEN
左旋G(X)
ENDIF
左旋P(X)
ENDIF
P(X)不为空
WHILE
ENDFUNC
买红妹近况对于伪码中的左旋和右旋操作,之前就已经图示过(虽然没有明说),那就是图一中的第一个(表示的是右旋,左旋是对称的),如果还不是很清楚则请看下图示:
图二
另外在单个的旋转过程中,如果G(X)还有父节点(假设为O)则先断开G(X)与O之间的连接即只考虑以G(X)为根节点的子树(假设为T),旋转完之后再连接节点X和节点O即X 占据旋转前G(X)的位置(注意:旋转后X变成了T的根节点)。
最后,值得一说的是,两种伸展伪码虽然都可以对伸展树进行伸展操作,但是对于同一伸展树在同一节点开始伸展最后得到的伸展树结构不一定完全一致(接下来的实例讲解会看到这一情况),因为第二种伪码实现使得伸展树的旋转不再是成对进行。
伸展树的自底向上伸展实例:
伪码一的旋转过程以及结果
伪码二的旋转过程以及结果