前言:
伸展树的设计有两种设计模式,第一种 自低向上的设计方式,第二种 设计方式 自顶向下的设计方式,对于由低向上的设计,每一个结点树保留指向其父结点的的额外的结点, 第二种方式由sedgewick大神的《算法》一书感谢这一
位大神。
定义:
伸展树又叫自适应查找树,实质上二叉搜索树的的变形,允许各种类型的二叉树操作,操作的时间复杂度
O(logN),但是伸展树并不能保障最坏的情况,这个时间复杂度是平均的值,适合任意的操作序列。
自顶向下的旋转:边访问,边肢解树。
当我们沿着树向下搜索某一个结点X时候,将搜索路径上的结点及其子树进行移走---创建两个空树 左树和右
树,没有被移走的结点叫做中树,在伸展操作的过程中:
1.当前遍历结点X 是树的中树
2.左树L保留小于X的结点
3. 右树R 保留大于X的结点
树开始遍历时候 X 是 树的根T,L,R是空树,自上而下有三种模式:
1. Zig 情况(单旋):
如下图,在搜索到X,要查找的结点比X 小,并且 所查找结点Y刚好等于 所查找的点 这种方式其实可以合并在zigzag情况之中,最简单的情况,程序 Y 所查找的结点,Y 只需进行简单的单旋,Y 变成新中树的树根,X 连接到右树上。
2. Zig-Zig (一字型旋转):
所需要查找结点 比当前节点 X的孩子结点Y值还需要小,对 X进行 左旋操作 ,回到Zig情况 之后将左旋 X,Y及
其子树结点 连接到右子树上。首先是Y绕X右旋,然后将Z变成新的中树根节点。将Y及其子树移到右树中。注意右树中挂载点的位置。
3. Zig-Zag 旋转(之字型旋转):
先将Y 右旋到 根 ,祖父结点X及其子树连接右树,变成图三的 Zag情况,接下 对Z 进行左旋变成Zig情况,将父结点Y链接到左树 上 ,Zig-
Zag情况需要分分解成两个Zig ;
4. 合并:
所查找的结点X 已经找到,X ,L,R 合并,X 是合并新树的树根,L 比X结点小的 结点集合,R是比X大结点的集合,如果X 有左子树和右子树的话
L.right=X.left (X 的子树结点 还是在 左树L的右边,左树上点 都是右旋形成),R.Left=X.right
编程:
右连接:将当前根以及右子树连接到 右 树上 ,当前节点 左子树结点 作为新根(下一次遍历的当前节点);
提示: nullPoint 表示逻辑上的 null的概念 ,header 结点 记录 左树和 右树的首地址,不能丢弃否则找不到 分解之后的左右树
局部变量 LeftTreeMax。rightTreeMin 为了跟随向下遍历 时候 增加 左 右 树长度
/** 伸展树
* nullpoint 表示逻辑上的null
* 建立空树 左 树 和 右 树
*/
public class SplayTree <AnyType extends Comparable<? super AnyType> >{
private static class BinaryNode<AnyType>{
AnyType Element;
BinaryNode<AnyType> left;
BinaryNode<AnyType> right;
public BinaryNode(AnyType Element) {
this(Element,null,null);
}
public BinaryNode(AnyType element, BinaryNode<AnyType> object, BinaryNode<AnyType> object2) {
this.Element=element;
this.left=object;
this.right=object2;
}
}
private BinaryNode<AnyType> root;
private BinaryNode<AnyType> nullNode;
// for Splay 记录 左树 和右树的首地址
private BinaryNode<AnyType> header=new BinaryNode<AnyType>(null);
// Use between different inserts
private BinaryNode<AnyType> newNode=null;
/** 自顶向下的伸展时候
* 创建 两个空树 */
private BinaryNode<AnyType> splay(AnyType x,BinaryNode<AnyType> t)
{
BinaryNode<AnyType> leftTreeMax,rightTreeMin;
header.left=header.right=nullNode;
leftTreeMax=rightTreeMin=header;
nullNode.Element=x;
while(true)
{
if(x.compareTo(t.Element)<0)
{
if(x.compareTo(t.left.Element)<0) // 一字型旋转 不是简单单旋 ,点 比 X,Y小,X,Y连接到右树上
t=roateWithLeftChild(t);// AVL
if(t.left ==nullNode)
break;// 没有找到
// Link Right 右连接:将当前根及其右子树连接到右树上。左子结点作为新根,向下遍历更新 rightTreeMin
rightTreeMin.left=t;
rightTreeMin=t;
t=t.left;// 左 结点 是 目标 Z 的子树下
}
else if(x.compareTo(t.Element)>0)
{
if(x.compareTo(t.right.Element)>0)
{
t=roateWithRightChild(t);
if(t.right==nullNode)
break;
leftTreeMax.right=t;
leftTreeMax=t;// 向下移动 用 header结点 记录 初始位置不怕 首地址 丢失
t=t.right;
}
}
else {
break;
}
// 找到之后合并
leftTreeMax.right=t.left;
rightTreeMin.left=t.right;
// header 结点 记录 右树 最开始地址 ,header 的左子树 记录比所查找点 大的结点 由 rightTreeMin.left=t
t.left=header.right;
t.right=header.left;
return t;
}
}
/**
* 将 插入 X 做成 新根 每插入 一次 进行splay*/
public void insert(AnyType x)
{
if(newNode==null)
newNode=new BinaryNode<AnyType>(null);
newNode.Element=x;
if(root==nullNode)
{
newNode.left=nullNode;
newNode.right=nullNode;
root=newNode;
}
else // 围绕新插入值 x 伸展开展开 root ,root。left 伸展开 之后 值一定比x小,已经存在 不insert
{
root=splay(x, root);
if(x.compareTo(root.Element)<0)
{
newNode.left=root.left;
newNode.right=root;
root.left=nullNode;
root=newNode;
}
else if(x.compareTo(root.Element)>0)
{
newNode.right=root.right;
newNode.left=root;
root.right=nullNode;
root=newNode;
}
else {
return;
}
newNode=null;// 为了下一次插入 分配 不同地址的 结点。
}
}
/**查找目标经过旋转 到 中间派*/
private BinaryNode<AnyType> roateWithLeftChild(BinaryNode<AnyType> k1) {
BinaryNode<AnyType> k2=k1.left;
k1.left=k2.right;
k2.right=k1;
return k2;
}
private BinaryNode<AnyType> roateWithRightChild(BinaryNode<AnyType> k2) {
BinaryNode<AnyType> k1=k2.right;
k2.right=k1.left;
k1.left=k2;
return k1;
}
}