Java基础 - 树堆(Treap = tree+heap)

Treap是用来排序(Sort)的一种数据结构(Data Structure)。
reap是随机查找二叉平衡树。 Treap发音为tree+ Heap。顾名思义, Treap把 BST和 Heap结合了起来。它和 BST一样满足许多优美的性质,而引入堆目的就是为了维护平衡。 Treap在 BST的基础上,添加了一个修正值。在满足 BST性质的上,Treap节点的修正值还满足最小堆性质。最小堆性质可以被描述为每个子树根节点都小于等于其子节点。
(1) Treap的特点 
1.  Treap简明易懂。Treap只有两种调整方式,左旋和右旋。而且即使没有严密的数学证明和分析,Treap的构造方法啊,平衡原理也是不难理解的。只要能够理解 BST和堆的思想,理解 Treap当然不在话下。 
2.  Treap易于编写。Treap只需维护一个满足堆序的修正值,修正值一经生成无需修改。相
比较其他各种平衡树, Treap拥有最少的调整方式,仅仅两种相互对称的旋转。所以 Treap当之无愧是最易于编码调试的一种平衡树。 
3.  Treap稳定性佳。Treap的平衡性虽不如 AVL,红黑树, SBT等平衡树,但是 Treap也不会退化,可以保证期望 O(logN)的深度。Treap的稳定性取决于随机数发生器。 
4.  Treap具有严密的数学证明。Treap期望 O(logN)的深度,是有严密的数学证明的。但这不是介绍的重点,大多略去。 
5.  Treap具有良好的实践效果。各种实际应用中, Treap的稳定性表现得相当出色,没有因为任何的构造出的数据而退化。于是在信息学竞赛中,不少选手习惯于使用 Treap,均取得了不俗的表现。

一棵treap是一棵修改了结点顺序的二叉查找树,如图,显示一个例子,通常树内的每个结点x都有一个关键字值key[x],另外,还要为结点分配priority[x],它是一个独立选取的随机数。

假设所有的优先级是不同的,所有的关键字也是不同的。treap的结点排列成让关键字遵循二叉查找树性质,并且优先级(有的地方也叫修正值,是一个随机数)遵循最小堆顺序性质:
1.如果left是u的左孩子,则key[left] < key[u].
2.如果right是u的右孩子,则key[right] > key[u].

3.如果child是u的孩子,则priority[child] > priority[u].

4.如果priority[vi] < priority[vj].则vi相对于vj而言更接近根节点。

这两个性质的结合就是为什么这种树被称为“treap”的原因,因为它同时具有二叉查找树和堆的特征。(在关键字上它满足二叉排序树,在优先级上他满足小顶堆)。

用以下方式考虑treap会有帮助。假设插入关联关键字的结点x1,x2,...,xn到一棵treap内。结果的treap是将这些结点以它们的优先级(随机选取)的顺序插入一棵正常的二叉查找树形成的,亦即priority[xi] < priority[xj]表示xi在xj之前被插入。
在算法导论的12.4节中,其证明了随机构造的二叉查找树的期望高度为O(lgn),因而treap的期望高度亦是O(lgn)。

1.treap插入操作:
1.按照二叉树的插入方法,将结点插入到树中
2.根据堆的性质(我们这里为最小堆)和优先级的大小调整结点位置。

2.treap删除操作:
1.找到相应的结点
2.若该结点为叶子结点,则直接删除;
若该结点为只包含一个叶子结点的结点,则将其叶子结点赋值给它;
若该结点为其他情况下的节点,则进行相应的旋转,直到该结点为上述情况之一,然后进行删除。

3、如何使treap平衡

Treap中的节点不仅满足BST的性质,还满足最小堆的性质。因此需要通过旋转来调整二叉树的结构,在维护Treap的旋转操作有两种:左旋和右旋,(注意:无论怎么旋转二叉查找树的性质是不能改变的)

4.查找

和一般的二叉搜索树一样,但是由于Treap的随机化结构,可以证明Treap中查找的期望复杂度是O(log n)。

5.分离

要把一个Treap按大小分成两个Treap,只要在需要分开的位置加一个虚拟节点,然后旋至根节点删除,左右两个子树就是得出的两个Treap了。根据二叉搜索树的性质,这时左子树的所有节点都小于右子树的节点。时间相当于一次插入操作的复杂度,也就是O(log n)。

6、查找最大值和最小值

根据Treap的性质可以看出最左非空子节点就是最小值,同理最右非空子节点就是最大值(同样也是BST的性质)

7、前驱与后继

定义:前驱,查找该元素在平衡树中不大于该元素的最大元素;后继查找该元素在平衡树中不小于该元素的最小元素。
从定义中看出,求一个元素在平衡树中的前驱和后继,这个元素不一定是平衡树中的值,而且如果这个元素就是平衡树中的值,那么它的前驱与后继一定是它本身。
求前驱的基本思想:贪心逼近法。在树中查找,一旦遇到一个不大于这个元素的值的节点,更新当前的最优的节点,然后在当前节点的右子树中继续查找,目的是希望能找到
一个更接近于这个元素的节点。如果遇到大于这个元素的值的节点,不更新最优值,节点的左子树中继续查找。直到遇到空节点,查找结束,当前最优的节点的值就是要求的前
驱。求后继的方法与上述相似,只是要找不小于这个元素的值的节点。
算法说明:

求前驱:
1. 从根节点开始访问,初始化最优节点为空节点;
2. 如果当前节点的值不大于要求前驱的元素的值,更新最有节点为当前节点,访问当前节点的右子节点;
3. 如果当前节点的值大于要求前驱的元素的值,访问当前节点的左子节点;
4. 如果当前节点是空节点,查找结束,最优节点就是要求的前驱。

求后继:
1. 从根节点开始访问,初始化最优节点为空节点;
2. 如果当前节点的值不小于要求前驱的元素的值,更新最有节点为当前节点,访问当前节点的左子节点;
3. 如果当前节点的值小于要求前驱的元素的值,访问当前节点的右子节点;
4. 如果当前节点是空节点,查找结束,最优节点就是要求的后继。


8、当前节点子树的大小

Treap 是一种排序的数据结构,如果我们想查找第 k 小的元素或者询问某个元素在 Treap 中从小到大的排名时,我们就必须知道每个子树中节点的个数。我们称以一个子树的所有节点的权值之和,为子树的大小。由于插入、删除、旋转等操作,会使每个子树的大小改变,所以我们必须对子树的大小进行动态的维护。
对于旋转,我们要在旋转后对子节点和根节点分别重新计算其子树的大小。
对于插入,新建立的节点的子树大小为 1。在寻找插入的位置时,每经过一个节点,都要先使以它为根的子树的大小增加 1,再递归进入子树查找。
对于删除,在寻找待删除节点,递归返回时要把所有的经过的节点的子树的大小减少 1。要注意的是,删除之前一定要保证待删除节点存在于 Treap 中。
下面给出左旋操作如何计算子树大小的代码,右旋很类似。
//这里需要注意的是,每个节点可能有重复的,重复的数目是用cnt来记录的,因此最后需要加上cnt


9、查找第K小元素

首先,在一个子树中,根节点的排名取决于其左子树的大小,如果根节点有权值 cnt,则根节点 P 的排名是一个闭区间 A,且 A = [ P->left->size + 1, P->left->size + P->cnt]。根据此,我们可以知道,如果查找排名第 k 的元素,k∈A,则要查找的元素就是 P 所包含元素。如果 kA,则排名第 k 的元素一定在右子树中,是右子树排名第 k-( P->left->size + P->cnt)的元素( P->left->size指的是p节点左子树的大小
算法思想:
1. 定义 P 为当前访问的节点,从根节点开始访问,查找排名第 k 的元素;                                                          
2. 若满足  P->left->size  + 1 <=k <= P->left->size + P->cnt,则当前节点包含的元素就是排名第 k 的元素;
3. 若满足 k < P->left->size + 1,则在左子树中查找排名第 k 的元素;
4. 若满足 k > P->left->size  + P->cnt,则在右子树中查找排名第 k-( P->left->size  + P->cnt)的元素。

10、求某个元素的排名

算法思想:
1. 定义 P 为当前访问的节点,cur 为当前已知的比要求的元素小的元素个数。从根节点开始查找要求的元素,初始化 cur 为 0;
2. 若要求的元素等于当前节点元素,要求的元素的排名为区间[P->left->size + cur + 1, P->left->size + cur + P->cnt]内任意整数;
3. 若要求的元素小于当前节点元素,在左子树中查找要求的元素的排名;
4. 若要求的元素大于当前节点元素,更新 cur 为 cur + P->left->size+P->cnt,在右子树中查找要求的元素的排名。

下面是代码实现:

package com.yc.tree;

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

public class THead >{
	public class Node{
		T data;
		int priority; //随机权值(修改值fix)
		Node parent;
		Node left;
		Node right;
		public Node(T data, int priority, Node parent, Node left, Node right){
			this.data = data;
			this.priority = priority;
			this.parent = parent;
			this.left = left;
			this.right = right;
		}
		public String toString(){
			return "[data="+data+",priority="+priority+"]";
		}
	}
	//根
	private Node root;
	//
	private Random rd;
	//
	private static final int DEFAULT_RD = 1 << 10;
	
	public THead(){
		root = null;
	}
	public THead(T data){
		rd = new Random();
		root = new Node(data, rd.nextInt(DEFAULT_RD), null, null, null);
	}
	public THead(T data, int priority){
		root = new Node(data, priority, null, null, null);
	}
	
	//按照随机的修改值添加节点
	public void add(T data){
		rd = new Random();
		add(data, rd.nextInt(DEFAULT_RD));
	}
	//按照指定的修改值添加节点
	public void add(T data, int priority){
		Node newNode = new Node(data, priority, null, null, null);
		if(root == null){
			root = newNode;
		}else{
			//找新插入节点的所属的父节点
			Node current = root;
			Node parent = current;
			int result = 0;
			while(current != null){
				parent = current;
				result = data.compareTo(current.data);
				if(result > 0){ //新插入节点的data域大于当前节点的data域(这里也埋下了当data域相等时会把新节点往左子树插)
					current = current.right;
				}else{
					current = current.left;
				}
			}
			
			if(result > 0){ 
				parent.right = newNode;
			}else{
				parent.left = newNode;
			}
			newNode.parent = parent;
			parent = null;  //释放临时节点
			
			reBalanceForAdd(newNode);
			
		}
	}
	//删除节点
	public void remove(T data){
		Node node = getNode(data);
		if(node != null){
			if(node.left == null && node.right == null){ //如果是叶子节点
				removeNeitherLeftAndRight(node);
			}else if(node.left != null && node.right == null){ //只有左子节点
				removeOnlyHasLeft(node);
			}else if(node.right != null && node.left == null){ //只有右子节点
				removeOnlyHasRight(node);
			}else{ //既有左子树又有右子树
				removeHaveLeftAndRight(node);
			}
		}
	}
	//最大值
	public T maxData(){
		Node current = root;
		Node maxNode = current;
		while(current != null){
			maxNode = current;
			current = current.right;
		}
		if(maxNode != null){
			return maxNode.data;
		}else{
			return null;
		}
	}
	//最小值
	public T minData(){
		Node current = root;
		Node minNode = current;
		while(current != null){
			minNode = current;
			current = current.left;
		}
		if(minNode != null){
			return minNode.data;
		}else{
			return null;
		}
	}
	//前驱
	public T frontData(T data){
		Node current = root;//当前节点
		Node frontNode = null;//前驱节点
		int result = 0;
		while(current != null){
			result = data.compareTo(current.data);
			if(result == 0){
				return current.data;
			}else if(result > 0){
				frontNode = current;
				current = current.right;
			}else{
				current = current.left;
			}
		}
		if(frontNode != null){
			return frontNode.data;
		}
		return null;
	}
	//后继
	public T descendantData(T data){
		Node current = root;//当前节点
		Node frontNode = null;//后继节点
		int result = 0;
		while(current != null){
			result = data.compareTo(current.data);
			if(result == 0){
				return current.data;
			}else if(result < 0){
				frontNode = current;
				current = current.left;
			}else{
				current = current.right;
			}
		}
		if(frontNode != null){
			return frontNode.data;
		}
		return null;
	}
	//8、当前节点子树的大小
	//9、查找第K小元素
	//10、求某个元素的排名
	//对于上面的三个问题需要在Node节点添加两个域Size(表示子树中节点个数),ctn(表示子树中重复Data域的节点个数)
	//这里就不做了,希望有兴趣的同学去完成
	private void removeNeitherLeftAndRight(Node node){
		if(root == node){
			root = null;
		}else{
			if(node == node.parent.left){
				node.parent.left = null;
				node.parent = null;
			}else{
				node.parent.right = null;
				node.parent = null;
			}
		}
	}
	private void removeOnlyHasLeft(Node node){
		node.left.parent = node.parent;
		if(node.parent != null){
			if(node == node.parent.left){
				node.parent.left = node.left;
			}else{
				node.parent.right = node.left;
			}
		}
		
		if(root == node){
			root = node.left;
		}
		node.parent = node.left = node.right = null;
	}
	private void removeOnlyHasRight(Node node){
		node.right.parent = node.parent;
		if(node.parent != null){
			if(node == node.parent.left){
				node.parent.left = node.right;
			}else{
				node.parent.right = node.right;
			}
		}
		
		if(root == node){
			root = node.right;
		}
		node.parent = node.left = node.right = null;
	}
	private void removeHaveLeftAndRight(Node node){
		int result = 0;
		while(node.left != null && node.right != null){
			result = node.left.priority - node.right.priority;
			if(result <= 0){ //左子节点的修改值小于等于右子节点,则进行右旋
				type_right(node.left);
			}else{
				type_left(node.right);
			}
		}
		if(node.left == null && node.right == null){ //如果是叶子节点
			removeNeitherLeftAndRight(node);
		}else if(node.left != null && node.right == null){ //只有左子节点
			removeOnlyHasLeft(node);
		}else if(node.right != null && node.left == null){ //只有右子节点
			removeOnlyHasRight(node);
		}
	}
	//添加节点后都THead进行修复
	private void reBalanceForAdd(Node node) {
		//1.递归
		if(node.parent != null){
			int result = node.priority - node.parent.priority;
			if(result < 0){
				if(node == node.parent.left){
					type_right(node);
					reBalanceForAdd(node);
				}else if(node == node.parent.right){
					type_left(node);
					reBalanceForAdd(node);
				}
			}else{
				return;
			}
		}
		//2.循环(楼主不会写QAQ.)
		/*int result = node.priority - node.parent.priority;
		while(node.parent != null && result < 0){
			if(result < 0){
				if(node == node.parent.left){
					type_right(node);
				}else{
					type_left(node);
				}
			}
		}*/
	}

	/**
	 * 节点右旋
	 * @param node
	 * 
	 * 			 │			│
	 * 		   p─┘          └─l
	 * 		  ││     ->		  ││ 
	 * 		l─┘└  			 ─┘└─p
	 * 		││					 ││
	 * 	   ─┘└				    ─┘└
	 * 	
	 */
	private void type_right(Node l){
		Node p = l.parent;
		
		l.parent = p.parent;
		if(p.parent != null){
			if(p == p.parent.left){
				p.parent.left = l;
			}else{
				p.parent.right = l;
			}
		}
		
		p.left = l.right;
		if(l.right != null){
			l.right.parent = p;
		}
		
		l.right = p;
		p.parent = l;
		
		if(root == p){
			root = l;
		}
	}
	/**
	 * 节点左旋
	 * @param r
	 * 
	 * 	│						 │
	 * 	└─p					   r─┘
	 *    ││     	->        ││
	 *   ─┘└─r				p─┘└─
	 *      ││				││
	 *     ─┘└─			   ─┘└─
	 */
	private void type_left(Node r){
		Node p = r.parent;
		
		r.parent = p.parent;
		if(p.parent != null){
			if(p == p.parent.left){
				p.parent.left = r;
			}else{
				p.parent.right = r;
			}
		}
		
		p.right = r.left;
		if(r.left != null){
			r.left.parent = p;
		}
		
		r.left = p;
		p.parent = r;
		
		if(root == p){
			root = r;
		}
	}
	
	//获得指定元素的节点
	private Node getNode(T data){
		Node current = root;
		if(current == null){
			return null;
		}else{
			int result = 0;
			while(current != null){
				result = data.compareTo(current.data);
				if(result > 0){
					current = current.right;
				}else if(result < 0){
					current = current.left;
				}else{
					return current;
				}
			}
			return null;
		}
	}
	//广度优先遍历
	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) {
		THead tree = new THead();
		tree.add(12, 6);
		tree.add(8, 12);
		tree.add(23, 20);
		tree.add(16, 3);
		tree.add(45, 18);
		tree.add(2, 7);
		tree.add(9, 42);
		tree.add(16, 15);
		System.out.println( tree.breadthFirstSearch());
		System.out.println("9的前驱是:" + tree.frontData(9));
		System.out.println("9的后继是:" + tree.descendantData(9));
		tree.remove(12);
		System.out.println( tree.breadthFirstSearch());
	}
}



测试结果为:

[[data=16,priority=3], [data=12,priority=6], [data=45,priority=18], [data=2,priority=7], [data=16,priority=15], [data=23,priority=20], [data=8,priority=12], [data=9,priority=42]]
9的前驱是:9
9的后继是:9
[[data=16,priority=3], [data=2,priority=7], [data=45,priority=18], [data=8,priority=12], [data=23,priority=20], [data=16,priority=15], [data=9,priority=42]]



你可能感兴趣的:(Java基础)