Java基础 - 替罪羊树(Scapegoat Tree)

package com.yc.tree;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

/**
 * @author wb
 * @param 
 * 
 * 替罪羊树的定义:
 * 1.是一种二叉排序树
 * 2.根节点存储了树的节点总数n和上次重建后的节点个数n上次;
 * 3.总能保持宽松的α高度平衡,即h<= hα + 1;
 * 
 * 对于排序二叉树的根节点:
 * α的高度平衡 :h <= hα (其中 hα = log(1/α)n = -(logn/logα)),也就是(AVL,红黑树)
 * 宽松α的高度平衡 :h <= hα + 1(其中hα = log(1/α)n = -(logn/logα))
 * α权重平衡:(n左  <= α*n) && (n右  <= α*n)
 * 
 * 当n一定时:
 * α越小,树越稠密,插入效率越低,查询效率越高
 * α越大,树越稀疏,插入效率越高,查询效率越低
 * 
 * 从上面的三个式子可以看出:
 * 	满足 α权重平衡的树一定满足 α的高度平衡;满足 α的高度平衡的树一定满足宽松 α的高度平衡;
 */
public class ScapegoatTree >{
	private class Node{
		T data;
		Node parent;
		Node left;
		Node right;
		public Node(T data, Node parent, Node left, Node right){
			this.data = data;
			this.parent = parent;
			this.left = left;
			this.right = right;
		}
		public String toString(){
			return "[data="+data+"]";
		}
	}
	//根节点
	private Node root;
	//上次修改的节点数
	private int lastModifyNodeCount = 0;
	//节点总数好像也要记
	private int NodeCount = 0;
	//阀值α
	private static final double threshold = 0.57;

	public ScapegoatTree(){
		root = null;
	}
	public ScapegoatTree(T data){
		root = new Node(data, null, null, null);
		NodeCount ++;
	}
	public Node root(){
		return this.root;
	}
	/**
	 * 将指定数据元素data添加到该替罪羊树
	 * @param data:指定数据元素
	 */
	public void add(T data){
		if(root == null){
			root = new Node(data, null, null, null);
			NodeCount ++;
		}
		else{
			//求得带加入节点的父节点
			Node parent = parent(data);
			if(parent != null){ //没有相同的数据元素节点
				int result = data.compareTo(parent.data);
				Node node = new Node(data, parent, null, null);
				if(result > 0){
					parent.right = node;
				}else{
					parent.left = node;
				}
				NodeCount ++;

				/*double ha = (-1) * Math.log10(NodeCount)/Math.log10(threshold);
				int deep_1 = deep_1(root); 
				if(deep_1 <= ha){//满足阿尔法的高度平衡
					return;
				}else{*/
				//System.out.println("打破了树的阿尔法高度平衡:" + node.data);
				//这个地方有两种方案找出替罪羊节点。
				//1.从新插入的节点入手一层一层向上回溯直到找到第一个不满足α权重平衡的节点,即!((n左  <= α*n) && (n右  <= α*n))
				//但是这里需要记录每个节点下面的节点总数,这就需要在Node内部类中增加一个size域来保存它,从而增加了内存开销,也就是空间换时间吧.

				//2.从网上找的:从插入位置开始一层一层往上回溯的时候,对于每一层都进行一次判断h(v) > log(1/alpha )(size(tree)),
				//一直找到最后一层不满足该条件的层序号(也就是从根开始的第一层不满足该条件的层序号,或者说是最接近根的一层,当然也包括根本身),
				//然后从该层开始重构以该层为根的子树。这种方法的缺点是每次回溯比较都要计算一次h(v)。也就是遍历树了,虽然不一定是遍历整个树。但还是耗时啊。
				//上面式子的size(tree)我有两种理解:
				//	①size(tree)指的是整棵树的总节点数,如果是这样,那么它的替罪羊节点永远都会是根节点。原因如下:
				//	我们假设存在一点满足   h某节点  > log(1/alpha )(size(tree)),那么它的根节点也一定满足
				//				  h根 > h某节点  > log(1/alpha )(size(tree));
				//	又由于它是在找一个更接近根的替罪羊,所以替罪羊必定为根。这种做法也就是你不停地往树中插入元素时,当达到一定程度(h根 > log(1/alpha )(size(tree))),
				//	就要对整棵树进行重构。
				//	②size(tree)指的是往上一层一层回溯时以某层为根节点的节点总数,如果是这样的话,那么它的替罪羊节点可能为根,也可能不为根。
				//	这种理解就需要记录每个节点下面的节点总数,这就需要在Node内部类中增加一个size域来保存以它为根的节点总数.
				//	我们假设存在一点刚好是根节点的右子节点满足  h右  > log(1/alpha )(size(tree右))
				//								h根 >= h右 + 1
				//								h根 < log(1/alpha )(size(tree右)+size(tree左) + 1)
				//	结合上面三个式子可以看出: log(1/alpha )(size(tree右)) + 1 < h右 + 1 <= h根 < log(1/alpha )(size(tree右)+size(tree左) + 1);
				//	即log(1/alpha)(size(tree右)*(1/alpha)) < log(1/alpha )(size(tree右)+size(tree左) + 1);
				//	又由于alpha的取值在[0.5, 1],所以 上式就变成了 size(tree右)+size(tree右) < size(tree右)+size(tree左) + 1;
				//	即: size(tree右) nodes = clap(node);
		pickUp(nodes, 0, nodes.size() / 2, nodes.size() - 1);
		return nodes.get(nodes.size() / 2);
	}
	//拎起来
	private void pickUp(List nodes, int lstart, int index, int rend){
		if(nodes != null){
			Node current = nodes.get(index);
			current.left = current.right = null;  //这里的current.parent 不能赋为null
			//index索引处节点的左子节点
			if(lstart <= index - 1){
				Node lnext = nodes.get( (index + lstart) / 2);
				current.left = lnext;
				lnext.parent = current;
				//这个很重要,当某节点为重构树中的叶子节点时,一定要把它的左、右子节点赋为null;不然遍历的时候无穷无尽,导致stack异常
				if(lstart == index - 1){ 
					lnext.left = lnext.right = null;
				}
				pickUp(nodes, lstart, (index + lstart) / 2, index - 1);
			}
			//index索引处节点的右子节点
			if(index + 1 <= rend){
				Node rnext = nodes.get((index + rend ) / 2 + 1);
				current.right = rnext;
				rnext.parent = current;

				if(rend == index + 1){
					rnext.left = rnext.right = null;
				}
				pickUp(nodes, index + 1, (index + rend ) / 2 + 1, rend);
			}
		}
	}
	//暴力拍平 = 中序遍历
	private List clap(Node node){
		List nodes = new ArrayList();
		//Deque deque = new ArrayDeque();
		if(node.left != null){
			nodes.addAll(clap(node.left));
		}
		nodes.add(node);
		if(node.right != null){
			nodes.addAll(clap(node.right));
		}
		return nodes;
	}
	//找替罪羊
	private Node scapegoatNode(Node node){
		Node sgNode = null;
		Node current = node;
		double ha;
		int deep_1;
		while(current != null){
			ha = (-1) * Math.log10(NodeCount)/Math.log10(threshold); //替罪羊永远是根,其实没有必要回溯,只要对根进行判断。
			deep_1 = deep_1(current); 
			if(deep_1 > ha){
				sgNode = current;
				current = current.parent;
			}else{
				current = current.parent;
			}
		}
		return sgNode;
	}
	//根据指定数据元素找他的父节点
	private Node parent(T data){
		/*if(root == null){ //就这个程序而言没必要判断吧,因为前面已经判断了一次。
			return null;
		}
		else{*/
		Node current = root;
		Node parent = current;
		int result = 0;
		while(current != null){
			parent = current;
			result = data.compareTo(current.data);
			if(result > 0){
				current = current.right;
			}else if(result < 0){
				current = current.left;
			}else{ 
				//这里什么也不写,这就导致了如果要加入的数据元素跟树中已存在的节点data域相同时,
				//不会加入该替罪羊树。
				return null;
			}
		}
		return parent;
	}
	//高度h
	private int deep_1(Node node){
		return deep(node) - 1;
	}
	//求以某节点为根的高度 
	public int deep(Node node){
		if(node != null){
			if(node.left == null && node.right == null){
				return 1;
			}
			int leftDeep = deep(node.left);
			int rightDeep = deep(node.right);
			return leftDeep >= rightDeep ? leftDeep + 1 : rightDeep + 1;
		}
		return 0;
	}
	/**
	 * 删除数据元素为data的节点
	 * @param data
	 * 
	 * 对于替罪羊树的删除有两种做法:
	 * 1、向排序二叉树一样删除,然后进行一次     删除某节点后的节点总数n < 阀值α * 上次重建树的节点个数 lastModityNodeCount 判断。
	 * 若满足,再次重构树。
	 * 2.伪删除:替罪羊树的删除节点并不是真正的删除,而是惰性删除(即给节点增加一个已经删除的标记,删除后的节点与普通节点无异,只是不参与查找操作而已)
	 * 很显然这种做法又要在Node内部类中新增加一个标记域。浪费空间。
	 * 
	 * 这里我是第一种做法
	 */
	public void remove(T data){
		Node node = find(data);
		if(node == null){
			return;
		}else{
			if(node.left == null && node.right == null){ //node为叶子节点
				if(node.parent == null){//node是根
					root = null;
				}else{
					if(node == node.parent.left){
						node.parent.left = null;
					}else{
						node.parent.right = null;
					}
					node.parent = null;
				}
				NodeCount --;
			}else if(node.left != null && node.right == null){//node只有左子树
				if(node.parent == null){//node是根
					root = node.left;
					node.left.parent = null;
					node.left = null;
				}else{
					node.left.parent = node.parent;
					if(node == node.parent.left){
						node.parent.left = node.left;
					}else{
						node.parent.right = node.left;
					}
					node.parent = node.left = null;
				}
				NodeCount --;
			}else if(node.right != null && node.left == null){ //node只有右子节点
				if(node.parent == null){//node是根
					root = node.right;
					node.right.parent = null;
					node.right = null;
				}else{
					node.right.parent = node.parent;
					if(node == node.parent.left){
						node.parent.left = node.right;
					}else{
						node.parent.right = node.right;
					}
					node.parent = node.right = null;
				}
				NodeCount --;	
			}else{
				//找到用于替换的后继节点
				Node succ = succNode(node);
				//替换数据
				node.data = succ.data;
				//删除替换的后继节点
				if(succ.parent == node){
					node.right = succ.right;
					if(succ.right != null){
						succ.right.parent = node;
					}
					succ.parent = succ.right = null;
				}else{
					succ.parent.left = succ.right;
					if(succ.right != null){
						succ.right.parent = succ.parent;
					}
					succ.parent = succ.right = null;
				}
				NodeCount --;
			}
			//删除过后看是否要重建整个树
			lastModifyNodeCount = lastModifyNodeCount == 0 ? NodeCount : lastModifyNodeCount;
			if(NodeCount < threshold*lastModifyNodeCount){
				Node newRoot = iDoNotKnow(root);
				
				root = newRoot;
				
				newRoot.parent = null;
				
				lastModifyNodeCount = NodeCount;
			}
		}
	}
	//
	public Node find(T data){
		Node current = root;
		int result;
		while(current != null){
			result = data.compareTo(current.data);
			if(result == 0){
				return current;
			}else if(result > 0){
				current = current.right;
			}else{
				current = current.left;
			}
		}
		return null;
	}
	/**
	 * 某节点的后继节点(用于删除左、右子树不为空的节点)
	 * @param node:某节点
	 * @return:后继节点
	 */
	public Node succNode(Node node){
		Node succ = null;
		int result;
		Node current = node;
		while(current != null){
			result = node.data.compareTo(current.data);
			if(result < 0){
				succ = current;
				current = current.left;
			}else{
				current = current.right;
			}
		}
		return succ;
	}
	//广度优先遍历
	public List breadthFirstSearch(){
		return cBreadthFirstSearch(root);
	}
	private List cBreadthFirstSearch(Node node) {
		List nodes = new ArrayList();
		Deque deque = new ArrayDeque();
		if(node != null){
			deque.offer(node);
		}
		while(!deque.isEmpty()){
			Node first = deque.poll();
			nodes.add(first);
			if(first.left != null){
				deque.offer(first.left);
			}
			if(first.right != null){
				deque.offer(first.right);
			}
		}
		return nodes;
	}
	public static void main(String[] args) {
		ScapegoatTree tree = new ScapegoatTree();
		tree.add(40);
		System.out.println("加入40后:"+tree.breadthFirstSearch());
		tree.remove(40);
		System.out.println("删除40后:"+tree.breadthFirstSearch());
		System.out.println();
		
		tree.add(10);
		tree.add(8);
		tree.add(12);
		tree.add(7);
		tree.add(9);
		tree.add(11);
		tree.add(14);
		tree.add(16);
		System.out.println("加入16后:"+tree.breadthFirstSearch());
		tree.add(18);
		System.out.println("加入18后:"+tree.breadthFirstSearch());
		System.out.println();
		// 9*0.57 = 5.13
		
		tree.remove(14);
		tree.remove(16);
		System.out.println("删除14,16后:"+tree.breadthFirstSearch());
		tree.remove(12);
		System.out.println("删除12后:"+tree.breadthFirstSearch());
		tree.remove(18);
		System.out.println("删除18后:"+tree.breadthFirstSearch());
	}
}

测试结果为:

加入40后:[[data=40]]
删除40后:[]

加入16后:[[data=10], [data=8], [data=12], [data=7], [data=9], [data=11], [data=14], [data=16]]
打破了树的阿尔法高度平衡:18
加入18后:[[data=11], [data=9], [data=16], [data=8], [data=10], [data=14], [data=18], [data=7], [data=12]]

删除14,16后:[[data=11], [data=9], [data=18], [data=8], [data=10], [data=12], [data=7]]
删除12后:[[data=11], [data=9], [data=18], [data=8], [data=10], [data=7]]
删除18后:[[data=9], [data=8], [data=11], [data=7], [data=10]]


下面是我给出的参考资料地址:

[Scapegoat Tree] & BZOJ3224

下面是百度文库里的两个地址:

替罪羊树

平衡树

你可能感兴趣的:(Java基础,平衡树,替罪羊树,数据结构)