splay树

来源:互联网 发布:贪心算法 最优解 编辑:程序博客网 时间:2024/05/21 22:32

splay树,中文名一般叫做伸展树。和treap树相同,作为平衡树,它也是通过左旋和右旋来调整树的结构。


和treap树不同的是,splay树不再用一个随机的权值来进行平衡,而是用固定的调整方式来使得调整之后的树会比较平衡。在左旋右旋的基础上,splay树定义了3个操作。
1. Zig

直接根据x节点的位置,进行左旋或右旋。该操作将x节点提升了一层。
2. Zig-Zig

若p不是根节点,还有父亲节点g,且p和x同为左儿子或右儿子,则进行Zig-Zig操作:当x,p同为左儿子时,依次将p和x右旋;当x,p同为右儿子时,依次将p和x左旋。注意此处不是将x连续Zig两次。该操作将x节点提升了两层。
3. Zig-Zag


若p不是根节点,还有父亲节点g,且p和x不同为左儿子或右儿子,则进行Zig-Zag操作:当p为左儿子,x为右儿子时,将x节点先左旋再右旋;当p为右儿子,x为左儿子时,将x节点先右旋再左旋。该操作将x节点提升了两层。
进一步在Zig,Zig-Zig和Zig-Zag操作上,splay树定义了splay操作。对于x以及x的祖先y,splay(x, y)表示对x节点进行调整,使得x是y的儿子节点。

splay(x,y):While(x.father!=y)p=x.fatherIf(p.father==y) Then//因为p的父亲是y,所以只需要将x进行Zig操作//就可以使得x的父亲变为yIf(p.left==x) Thenright-rotate(x)Elseleft-rotate(x)End IfElseg=p.fatherIf(g.left==p) ThenIf(p.left==x) Then//x,p同为左儿子,Zig-Zig操作right-rotate(p)right-rotate(x)Else//p为左,x为右,Zig-Zag操作left-rotate(x)right-rotate(x)End IfElseIf (p.right==x) Then//x,p同为右儿子,Zig-Zig操作left-rotate(p)left-rotate(x)Else //p为右,x为左,Zig-Zag操作right-rotate(x)left-rotate(x)End IfEnd IfEnd IfEnd While
在执行这个操作的时候,需要保证y节点一定是x节点祖先。值得一提的是,大多数情况下我们希望通过splay操作将x旋转至整棵树的根节点。此时只需令y=NULL即可实现。
splay树的插入和查询操作和普通的二叉搜索树没有什么大的区别,需要注意的是每次插入和查询结束后,需要对访问节点做一次splay操作,将其旋转至根。
<pre name="code" class="cpp">insert(key):node=bst_insert(key) //同普通的BST插入,node为当前插入的新节点splay(node, NULL)find(key):node=bst_find(key) //同普通的BST查找,node为查找到的节点splay(node,NULL)
同时由于splay的特性,我们还有两个特殊的查询操作。在树中查找指定数key的前一个数和后一个数。我们先将key旋转至根,那么key的前一个数一定是根节点左儿子的最右子孙,同时key的后一个数一定是根节点右儿子的最左子孙。
findPrev(key):splay(find(key),NULL)node=root.leftWhile(node.right)node=node.rightReturn nodefindNext(key):splay(find(key),NULL)node=root.rightWhile(node.left)node=node.leftReturn node
splay的删除可以采用和一般二叉搜索树相同的方法:即先找到节点key,若key没有儿子则直接删去;若key有1个儿子,则用儿子替换掉x;若key有2个儿子,则通过找到其前(或后)一个节点来替换掉它,最后将该节点splay到根。同时,这里还有另一种方法来完成删除操作:首先我们查找到key的前一个数prev和后一个数next。将prev旋转至根,再将next旋转为prev的儿子。此时key节点一定是next的左儿子。那么直接将next的左儿子节点删去即可。
delete(key):prev=findPrev(key)next=findNext(key)splay(prev,NULL)splay(next,prev)next.left=NULL
这里你可能会担心如果key是数中最小或者是最大的数怎么办?一个简单的处理方式是手动加入一个超级大和超级小的值作为头尾。这里有一个问题,假如要删除一个区间[a,b]的数该怎么做?因为要删除[a,b],那么我就要想办法把[a,b]的数旋转到一个子树上,再将这个子树删掉就行了。方法和删除一个数相同,我首先将a的前一个数prev和b的后一个数next找出来。同样将prev旋转至根,再将next旋转为prev的儿子。那么此时next的左子树一定就是所有[a,b]之间的数了!
deleteInterval(a,b):prev=findPrev(a)next=findNext(b)splay(prev,NULL)splay(next,prev)
如果a,b不在树中,把a,b插入树中,做完之后再删除。
splay树由于splay操作的使得其相较于treap树具有更大的灵活性,并且不再有随机性。其插入、查找和删除操作的均摊时间复杂度也都是O(logn)的。




0 0
原创粉丝点击