文章目录
1、伸展树简介
伸展树(Splay Tree)是特殊的二叉查找树(BST)。
它的特殊是指,它除了本身是棵二叉查找树之外,它还具备一个特点: 当某个节点被访问时,伸展树会通过旋转使该节点成为树根。这样做的好处是,下次要访问该节点时,能够迅速的访问到该节点。
通过之前的学习,知道连续m次查找,对于AVL树来说,共需要O(mlogn)时间。根据局部性原理,我们可以改进AVL树!!-----> 引入伸展树
局部性:刚被访问过的数据,极有可能很快地再次被访问。下一将要访问的节点,极有可能就在刚被访问过的节点附近。
1.2、特性
1.和普通的二叉查找树相比,具有任何情况下、任何操作的平摊O(log2n)的复杂度,时间性能上更好
2.和一般的平衡二叉树比如 红黑树、AVL树相比,维护更少的节点额外信息,空间性能更优,同时编程复杂度更低
3.在很多情况下,对于查找操作,后面的查询和之前的查询有很大的相关性。这样每次查询操作将被查到的节点旋转到树的根节点位置,这样下次查询操作可以很快的完成
4.可以完成对区间的查询、修改、删除等操作,可以实现线段树和树状数组的所有功能
2、 性能评价
无需记录节点高度或者平衡因子,编程简单——优于AVL树
分摊复杂度O(logN) —— 与AVL树相当
局部性强、缓存命中率极高时(即k<<n<<n)
效率甚至可以更高——自适应的O(logK)
任何连续的m次查找,都可在O(mlogk + nlogn)时间内完成
但是
仍不能保证单词最坏情况的出现
不适用于对效率敏感的场所
3、逐层伸展( X )
伸展方式有两种:1 逐层伸展 2双层伸展
逐层伸展:一步一步往上,自下而上,逐层单旋。直到V最终被推送至根。
但效率很低,最坏情况:对于下图依次访问1~7的话,每一周期总时间O(n^2),分摊时间O(n)。远小于AVL树的O(logN)
Zig-zig / zag-zag 的逐层旋转:
4、双层伸展 ( √ )
双层伸展:向上追溯两层,而非一层! (Tarjan提出)
实现O(log2n)量级的平摊复杂度依靠每次对伸展树进行查询、修改、删除操作之后,都进行旋转操作 Splay(x, root),该操作将节点x旋转到树的根部。
优点:① 折叠效果:一旦访问坏节点,对应路径的长度将随机减半
② 最好情况不会持续发生,单趟伸展操作,分摊O(logN)
时间
伸展树的旋转有六种类型,如果去掉镜像的重复,则为三种:
zig(zag)、zig-zig(zag-zag)、zig-zag(zag-zig)。
4.1 zig- zig 旋转
如下图所示。在逐层伸展方式中,先VP旋转,再VG旋转。在双层旋转中,先对祖父节点G进行PG的越级旋转,再进行VP的旋转!!!(和第3部分的图对比一下区别)
优点:在一棵退化成单链的伸展树中访问其最深的节点,经过伸展后树高大约为原先的1/2
4.2 zig- zag 旋转
如下图所示,VPG三者在之字型链上。此时,先对P节点进行PV的zig旋转,再对G节点进行GV的zag旋转,最后变为右图所示,V成为P和G的祖先节点。这个方法和AVL树双旋完全等效,与逐层伸展别无二致。
4.3 zig / zag 旋转
如果V只有父亲,没有祖父。就会出现下图所示。Parent(v) = root(T) 在每轮调整中,这种情况至多(在最后)出现一次。
5、节点
public class mySplayTree<AnyType extends Comparable<? super AnyType>>
{
/**
* Construct the tree.
*/
public mySplayTree( )
{
nullNode = new BinaryNode<AnyType>( null );
nullNode.left = nullNode.right = nullNode;
root = nullNode;
}
private BinaryNode<AnyType> newNode = null; // Used between different inserts
private BinaryNode<AnyType> header = new BinaryNode<AnyType>( null );
private BinaryNode<AnyType> root;
private BinaryNode<AnyType> nullNode;
}
6、查找
不论成功与否,总会把最后被访问的节点伸展到根。 —> 动态操作(回想一下之前学的二叉树、BVL树的查找都是静态操作噢)
private BinaryNode<AnyType> splay( AnyType x, BinaryNode<AnyType> t )
{
BinaryNode<AnyType> leftTreeMax, rightTreeMin;
header.left = header.right = nullNode;
leftTreeMax = rightTreeMin = header;
nullNode.element = x; // Guarantee a match
for( ; ; )
{
int compareResult = x.compareTo( t.element );
if( compareResult < 0 )
{
if( x.compareTo( t.left.element ) < 0 )
t = rotateWithLeftChild( t );
if( t.left == nullNode )
break;
// Link Right
rightTreeMin.left = t;
rightTreeMin = t;
t = t.left;
}
else if( compareResult > 0 )
{
if( x.compareTo( t.right.element ) > 0 )
t = rotateWithRightChild( t );
if( t.right == nullNode )
break;
// Link Left
leftTreeMax.right = t;
leftTreeMax = t;
t = t.right;
}
else
break;
}
leftTreeMax.right = t.left;
rightTreeMin.<