伸展树---(自顶向下的设计)

前言:

        伸展树的设计有两种设计模式,第一种 自低向上的设计方式,第二种 设计方式 自顶向下的设计方式,对于由低向上的设计,每一个结点树保留指向其父结点的的额外的结点, 第二种方式由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  >{
	
	private static class BinaryNode{
		AnyType Element;
		BinaryNode left;
		BinaryNode right;
		public BinaryNode(AnyType Element) {
			
			this(Element,null,null);
		}

		public BinaryNode(AnyType element, BinaryNode object, BinaryNode object2) {
			this.Element=element;
			this.left=object;
			this.right=object2;
		}
		}
	
	private BinaryNode root;
	private BinaryNode nullNode;
	
	// for Splay 记录 左树 和右树的首地址
	private  BinaryNode header=new BinaryNode(null);
	
	// Use between different inserts
	private BinaryNode newNode=null;
	
	/** 自顶向下的伸展时候
	 * 创建 两个空树 */
	private BinaryNode splay(AnyType x,BinaryNode t)
	{
		BinaryNode 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(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 roateWithLeftChild(BinaryNode k1) {
		BinaryNode k2=k1.left;
		k1.left=k2.right;
		k2.right=k1;
		return k2;
	}
	
	private BinaryNode  roateWithRightChild(BinaryNode k2) {
		BinaryNode k1=k2.right;
		k2.right=k1.left;
		k1.left=k2;
		
		
		
		return k1;
	}
}


你可能感兴趣的:(树)