Java基础 - 红黑树

package com.yc.tree;

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

/**
 * @author wb
 * @param <T>
 * 
 * 排序二叉树虽然可以快速检索,但在最坏的情况下,如果插入的节点集本身就是有序的,要么是由小到大排列,要么是由大到小排列,
 * 那么最后的排序二叉树将得到链表:所有节点只有左子节点,或者所有的节点只有右子节点。在这种情况下,排序二叉树变成了普通链表,
 * 其检索效率就会很低。
 * 
 * 为了改变排序二叉树存在的这种不足,Rudolf Bayer在1972年发明了另一种改进后的排序二叉树——红黑树,他将这种排序二叉树
 * 称为“对称二叉B树”,而红黑树这个名字则由Leo J.Guibas 和  Robert Sedgewick 在1978年首次提出。
 * 
 * 红黑树是一棵更高效的检索二叉树,因此常常用来实现关联数组。典型的,JDK提供的集合类TreeMap本身就是一棵红黑树的实现。
 * 	
 * 红黑树在原有的排序二叉树上增加了如下几个要求:
 * 性质(1):每个节点要么是红色,要么是黑色。
 * 性质(2):根节点永远是黑色。
 * 性质(3):所有的叶子节点都是空节点(即null),且是黑色的。
 * 性质(4):每个红色节点的两个子节点都是黑色的。(从每个叶子节点到根的路径上不会有两个连续的红色节点)
 * 性质(5):从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点。
 * 
 * 根据性质5,红黑树从根节点到每个叶子节点的路径都包含相同数量的黑色节点,因此从根节点到叶子节点的路径中包含的黑色节点数被
 * 称为树的“黑色高度(black-height)”。
 * 性质4则保证了从根节点到叶子节点的最长路径的长度不会超过任何其它路径的2倍。假设有一棵黑色高度为3的红黑树,从根节点到叶子节点的最短路径
 * 长度是2,改路径上全是黑色节点(黑色节点-黑色节点-黑色节点)。最长路径也只可能是4,在每个黑色节点之间插入一个红色节点(黑色节点-红色节点-
 * 黑色节点-红色节点-黑色节点),性质4保证绝不可能插入更多的红色节点。由此可见,红黑树中最长的路径就是一条红黑交替的路径。
 * 
 * 由此可以得出结论:对于给定的黑色高度为 N 的红黑树,从根到叶子节点的最短路径长度为 N-1 ,最长路径的长度为 2*(N-1)。
 *	******************************************************************************
 * 	提示:排序二叉树的深度直接影响了检索的性能。正如前面指出的,当插入节点本身就是有小到大排列时,排序二叉树将变成	*
 * 	一个链表,这种排序二叉树的检索性能最低:N 个节点的二叉树深度就是 N-1;								*
 *	******************************************************************************
 *
 *	红黑树通过上面这种限制来保证它大致是平衡的——因为红黑树的高度不会无限增高,这样能保证红黑树在最坏的情况下都是高效的,不会出现普通排序二叉树的情况。
 *
 *	*************************************************************************
 *	注意:红黑树并不是真正的平衡二叉树,但在实际应用中,红黑树的统计性能要高于平衡二叉树,但在极端情况下略差。	*
 *	*************************************************************************
 *
 *	由于红黑树是一种特殊的排序二叉树,因此对红黑树的只读操作和普通排序二叉树的只读操作完全相同,只是红黑树保持了大致平衡,
 *因此检索性能更好。但在红黑树上进行插入操作和删除操作会导致树不在符合红黑树的特征,因此插入操作和删除操作都需要进行一定的维护,
 *以保证插入节点、删除节点后的树依然是红黑树。
 *
 *插入操作:
 *①以排序二叉树的方法插入新节点,并将它设为红色。(如果设为黑色,就会导致根节点到叶子节点的路径上多一个额外的黑色节点,那么所经过该新添加黑色节点的
 *路径的长度都会比其他的 大1,这样将会导致很难调整。但是设为红色节点后,可能会导致出现两个连续的红色节点,在通过颜色调换和树旋转来调整即可)。
 *②这种颜色调换和树旋转就复杂了,下面将分情况进行介绍。介绍中将新插入的节点定义为N节点,把N节点的父节点定义为P节点,把P节点的兄弟节点定义为U节点,
 *把P节点的父节点定义为G节点。(在插入操作中,红黑树的性质1和性质3都是不变的,因此无需考虑这两个性质)
 *	Ⅰ、情形1:新节点N是树的根节点,没有父节点。
 *		在这种情况下,直接将他设为黑色以满足性质2。
 *	Ⅱ、情形2:新节点N的父节点P是黑色的。
 *		在这种情况下,新插入的节点是红色的,因此满足性质4(我只关心父节点)。而且因为新节点N有两个黑色叶子节点,但是由于新节点N是红色的,
 *		通过他的每个子节点的路径依然保持相同的黑色节点数,因此依然满足性质5.
 *	Ⅲ、情形3:父节点P和父节点的兄弟节点U都是红色的。
 *		在这种情况下,程序应该将P节点、U节点都设置为黑色,并将P节点的父节点G设置为红色(用来保持性质5)。现在,新节点N有了一个黑色的父节点P。
 *		由于从P节点、U节点到根节点的任何路径都必须通过G节点,这路径上的黑色节点数目没有发生改变(原来有叶子节点和G节点两个黑色节点,现在有叶子
 *		和P两个黑色节点)。
 *		经过上面的处理后,红色节点G的父节点也有可能是红色的,这就违反了性质4,因此还需要对G节点递归的进行整个过程(把G节点当成新插入的节点进行调整)。
 *		下图显示了这种操作:
 *					  	 G (B)									   G (R)
 *					  	 ││										   ││
 *		   (R) P ────────┘└────── U (R) 	--->	 (B) P ────────┘└────── U (B) 
 *			  ││		  null ──┘└── null				││			null ──┘└── null
 *	  (B) N ──┘└── null							(R) N ──┘└── null
 * null ──┘└── null							null ──┘└── null
 *								图11.28 插入节点后进行颜色调换
 *		虽然图11.28中绘制的是新节点N作为父节点P的左子节点的情形,但其实新节点N作为父节点P的右子节点的情形与图11.28完全相同。
 *
 *	Ⅳ、情形4:父节点P是红色的,而其兄弟节点U是黑色的或缺少;且新节点N是父节点P的右子节点,而父节点P又是其父节点G的左子节点。
 *		在这种情况下,对新节点和其父节点进行一次左旋转。接着,按情形5处理以前的父节点P(也就是把P当成新插入的节点)。这将导致某些路径
 *		通过他们以前不通过的新节点N或父节点P其中之一,但是这两个节点都是红色的,因此不会影响性质5.
 *		下图显示了对情形4的处理:
 *
 *				 		G (B)										G (B)
 *						││											││
 *		  (R) P ────────┘└────── U (B)    	--->	  (R) N ────────┘└────── U (B)
 *			 ││			  null ──┘└── null				 ││	  		  null ──┘└── null
 *	  null ──┘└── N (R)							 (R) P ──┘└── null	
 *		   null ──┘└── null					  null ──┘└── null
 *								图11.29 插入节点后的树旋转
 *		图11.29中P节点是G节点的左子节点,如果P节点是其父节点G的右子节点,那么上面的处理情况应该左、右对调一下。
 *								
 *	Ⅴ、情形5:父节点P是红色的,而其兄弟节点U是黑色的或缺少;且新节点N是父节点P的左子节点,而父节点P又是其父节点G的左子节点。
 *		在这种情形下,需要对节点G进行一次右旋转。在旋转产生的树中,以前的父节点P现在是新节点N和节点G的父节点。由于以前的节点G是黑色的(否则父节点P
 *		不可能是红色的),切换以前的父节点P和节点G的原色,使之满足性质4,。性质5也仍然保持满足,因为通过这三个节点中任何一个的所有路径以前都通过节点G
 *		现在他们都通过以前的父节点P。在各自的情况下,这都是三个节点中唯一的黑色节点。
 *		下图显示了对情形5的处理:
 *		
 *							G (B)									P (B)
 *						   ││										││
 *			 (R) P ────────┘└────── U (B)	  ---> 		 (R) N ─────┘└────── G (R)
 *				││		 	 null ──┘└── null		 null ──┘└── null  		│
 *		(R) N ──┘└── null													└────── U (B)
 *	 null ──┘└── null														null ──┘└── null 
 *										图11.30 插入节点后的颜色调换、树旋转
 *		图11.30中的P节点是G节点的左子节点,如果P节点是其父节点的右子节点,那么上面的处理情况应该左、右对调一下。
 *				
 *
 *删除操作:
 *	红黑树的删除操作比插入操作要稍微复杂一些,实际上也可按如下步骤进行:
 *		①以排序二叉树的方法删除指定节点。
 *		②进行颜色调换和树旋转,使之满足红黑树特征。
 *	关于红黑树的删除操作要进行的颜色调换和树旋转可以参考前面的红黑树插入时各个情况的处理。
 *
 *下面给出一棵红黑树的Java实现(更具体的建议大家去看Java集合框架中的TreeMap实现类,它就是红黑树在Java中的最好实现吧):				
 */
@SuppressWarnings("rawtypes")
public class RedBlackTree <T extends Comparable>{
	private static final boolean RED = false;
	private static final boolean BLACK = true;

	static class Node{
		Object data;
		Node parent;
		Node left;
		Node right;

		//节点默认颜色是黑色
		boolean color = BLACK;
		public Node(){
		}
		public Node(Object 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 + ", color:" + color + "]";
		}
		public boolean equals(Object obj) {
			if (this == obj){
				return true;
			}
			if(Node.class == obj.getClass()){
				Node target = (Node)obj;
				return data.equals(target.data) &&
						parent == target.parent &&
						left == target.left &&
						right == target.right;
			}
			return false;
		}
	}

	private Node root;

	public RedBlackTree(){
		root = null;
	}
	public RedBlackTree(T data){
		root = new Node(data, null, null, null);
	}

	/**
	 * 添加元素
	 * @param data
	 */
	@SuppressWarnings("unchecked")
	public void add(T data){
		if(root == null){
			root = new Node(data, null, null, null);
		}
		else{
			Node current = root;
			Node parent = null;

			int  tmp = 0;
			do{
				parent = current;
				tmp = data.compareTo(current.data);
				if(tmp > 0){
					current = current.right;
				}else{
					current = current.left;
				}
			}while( current != null);
			//此时的parent指向的就是 要添加节点的父节点。

			Node newNode = new Node(data, parent, null, null);

			if(tmp > 0){
				parent.right = newNode;
			}else{
				parent.left = newNode;
			}

			fixAfterAdd(newNode);
		}
	}

	/**
	 * 删除元素
	 * @param data
	 */
	public void remove(T data){
		//获取要删除的元素节点
		Node target = getNode(data);

		//如果左、右子树都不为空
		if(target.left != null && target.right != null){
			Node s = target.left;
			while( s.right != null){
				s = s.right;
			}
			//用s节点代替target节点
			target.data = s.data;
			target = s;
		}

		//开始修复它的替换节点,如果该替换节点不为null
		//个人觉得这段代码一石二鸟
		Node replacement = (target.left != null ? target.left : target.right);
		if(replacement != null){
			replacement.parent = target.parent;

			if(target.parent == null){
				root = replacement;
			}else if(target == target.parent.left){
				target.parent.left = replacement;
			}else{
				target.parent.right = replacement;
			}

			target.parent = target.left = target.right = null;

			if(target.color == BLACK){
				fixAfterRemove(replacement);
			}
		}else if(target.parent == null){
			root = null;
		}else{
			if(target.color == BLACK){
				fixAfterRemove(target);
			}
			if(target.parent != null){
				if(target == target.parent.left){
					target.parent.left = null;
				}else if(target == target.parent.right){
					target.parent.right = null;
				}

				target.parent = null;
			}
		}

	}

	/**
	 * 插入新节点 newNode 后对红黑树的维护
	 * @param newNode
	 */
	private void fixAfterAdd(Node node) {
		node.color = RED;
		
		//直到node节点的父节点不是根,且node节点的父节点不为红色
		while(node != null && node != root && node.parent.color == RED){
			//如果node的父节点P是其父节点G的左子节点
			if(parentOf(node) == leftChildOf(parentOf(parentOf(node)))){
				//获取node节点的父节点P的兄弟节点U
				Node uNode = rightChildOf(parentOf(parentOf(node)));
				
				if(colorOf(uNode) == RED){//如果node父节点P的兄弟节点的颜色为红色(情形3)
					//将node父节点P设为黑色
					setColor(parentOf(node), BLACK);
					//将node父节点P的兄弟节点U设为黑色
					setColor(uNode, BLACK);
					//将node父节点P的父节点G设为红色
					setColor(parentOf(parentOf(node)), RED);
					
					//情景3中可能G的父节点也为红色,所以循环递归调试
					node = parentOf(parentOf(node));
				}else{//如果node父节点P的兄弟节点的颜色为黑色(情形4或5)
					if(node == rightChildOf(parentOf(node))){//(情形4)
						node = parentOf(node);
						rotateLeft(node);
					}
					//将node的父节点(可能是N也可能是P)设为黑色
					setColor(parentOf(node), BLACK);
					//将node的父节点的父节点G设为红色
					setColor(parentOf(parentOf(node)), RED);
					//将G节点右旋转
					rotateRight(parentOf(parentOf(node)));
				}
			}
			//如果node的父节点P是其父节点G的右子节点
			else{
				Node uNode = leftChildOf(parentOf(parentOf(node)));
				
				if(colorOf(uNode) == RED){
					setColor(parentOf(node), BLACK);
					setColor(uNode, BLACK);
					setColor(parentOf(parentOf(node)), RED);
					
					node = parentOf(parentOf(node));
				}else{
					if(node == leftChildOf(parentOf(node))){
						node = parentOf(node);
						rotateRight(node);
					}
					
					setColor(parentOf(node), BLACK);
					setColor(parentOf(parentOf(node)), RED);
					
					rotateLeft(parentOf(parentOf(node)));
				}
			}
		}
		
		root.color = BLACK;
	}
	/**
	 * 删除节点后对红黑树的维护
	 * @param replacement :要删除节点的左或右子节点
	 */
	private void fixAfterRemove(Node node) {
		//至到 node 不是根节点,且node节点的颜色是黑色的
		while(node != root && colorOf(node) == BLACK){
			//如果node是其父节点的左子节点
			if(node == leftChildOf(parentOf(node))){
				Node uNode = rightChildOf(parentOf(node));
				//如果node父节点的兄弟节点是红色的
				if(colorOf(uNode) == RED){
					//设node父节点的兄弟节点为黑色
					setColor(uNode, BLACK);
					//设node节点的父节点为红色
					setColor(parentOf(node), RED);
					//以node节点的父节点向左旋转
					rotateLeft(parentOf(node));
					//再次将node节点的兄弟节点U设为node父节点的右子节点
					uNode = rightChildOf(parentOf(node));
				}
				//如果uNode的两个子节点都是黑色
				if(colorOf(leftChildOf(uNode)) == BLACK && colorOf(rightChildOf(uNode)) == BLACK){
					//将uNode设为红色
					setColor(uNode, RED);
					node = parentOf(node);
				}else{
					//如果只有uNode的右子节点是黑色
					if(colorOf(rightChildOf(uNode)) == BLACK){
						//将uNode的左子节点也设为黑色
						setColor(leftChildOf(uNode), BLACK);
						//将uNode设为红色
						setColor(uNode, RED);
						
						rotateRight(uNode);
						uNode = rightChildOf(parentOf(node));
					}
					//设置uNode的颜色和node父节点的颜色相同
					setColor(uNode, colorOf(parentOf(node)));
					//将node的父节点设为黑色
					setColor(parentOf(node), BLACK);
					//将uNode的右子节点设为黑色
					setColor(rightChildOf(uNode), BLACK);
					
					rotateLeft(parentOf(node));
					node = root;
				}
			}
			//如果node是其父节点的右子节点
			else{
				Node uNode = leftChildOf(parentOf(node));
				if(colorOf(uNode) == RED){
					setColor(uNode, BLACK);
					setColor(parentOf(node), RED);
					rotateRight(parentOf(node));
					uNode = leftChildOf(parentOf(node));
				}
				if(colorOf(leftChildOf(uNode)) == BLACK && colorOf(rightChildOf(uNode)) == BLACK){
					setColor(uNode, RED);
					node = parentOf(node);
				}else{
					if(colorOf(leftChildOf(uNode)) == BLACK){
						setColor(rightChildOf(uNode), BLACK);
						setColor(uNode, RED);
						rotateLeft(uNode);
						uNode = leftChildOf(parentOf(node));
					}
					setColor(uNode, colorOf(parentOf(node)));
					setColor(parentOf(node), BLACK);
					setColor(leftChildOf(uNode), BLACK);
					rotateRight(parentOf(node));
					node = root;
				}
			}
		}
		setColor(node, BLACK);
	}
	
	
	/**
	 * 左旋转
	 * 执行如下转换
	 * 		p				r
	 * 		│				│
	 * 		└──r	->   p──┘
	 * 	 	   │		 │
	 * 	 	q──┘		 └──q
	 * @param node
	 */
	private void rotateLeft(Node p) {
		if(p != null){
			Node r = p.right; //p的右子节点
			Node q = r.left;//p的右子节点的左子节点
			
			p.right = q;
			if(q != null){
				q.parent = p;
			}
			
			r.parent = p.parent;
			if(p.parent == null){
				root = r;
			}else if(p == p.parent.left){
				p.parent.left = r;
			}else{
				p.parent.right = r;
			}
			
			p.parent = r;
			r.left = p;
			
		}
	}
	/**
	 * 右旋转
	 * 执行如下转换
	 * 			p			l
	 * 			│			│
	 * 		 l──┘	->      └──p
	 * 	 	 │		 		   │
	 * 	 	 └──q			q──┘
	 * @param node
	 */
	private void rotateRight(Node p) {
		if(p != null){
			Node l = p.left; //p的左子节点
			Node q = l.right;//p的左子节点的右子节点
			
			p.left = q;
			if(q != null){
				q.parent = p;
			}
			
			l.parent = p.parent;
			if(p.parent == null){
				root = l;
			}else if(p == p.parent.left){
				p.parent.left = l;
			}else{
				p.parent.right = l;
			}
			
			p.parent = l;
			l.right = p;
			
		}
	}
	
	//返回指定节点的颜色
	private boolean colorOf(Node node){
		return node == null ? BLACK : node.color;
	}
	//为指定节点设置颜色
	private void setColor(Node node, boolean color){
		if(node != null){
			node.color = color;
		}
	}
	//返回指定节点的父节点
	private Node parentOf(Node node){
		return node == null ? null : node.parent;
	}
	//返回指定节点的左子节点
	private Node leftChildOf(Node node){
		return node == null ? null : node.left;
	}
	//返回指定节点的右子节点
	private Node rightChildOf(Node node){
		return node == null ? null : node.right;
	}
	
	/**
	 * 获取指定元素的节点
	 * @param data
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private Node getNode(T data) {
		Node current = root;
		int tmp = 0;
		while(current != null){
			tmp = data.compareTo(current.data);

			if(tmp > 0){
				current = current.right;
			}else if(tmp < 0){
				current = current.left;
			}else{
				return current;
			}
		}
		return null;
	}

	/**
	 * 广度优先遍历
	 * @return
	 */
	public List<Node> breadthFirstSearch(){
		List<Node> nodes = new ArrayList<Node>();
		Deque<Node> deque = new ArrayDeque<Node>();
		if(root != null){
			deque.offer(root);
		}

		while(!deque.isEmpty()){
			Node tmp = deque.poll();
			nodes.add(tmp);

			if(tmp.left != null){
				deque.offer(tmp.left);
			}
			if(tmp.right != null){
				deque.offer(tmp.right);
			}
		}
		return nodes;
	}

	/**
	 * 递归之中度遍历
	 * @return
	 */
	public List<Node> cLDR(){
		return cLDR(root);
	}
	public List<Node> cLDR(Node node){
		List<Node> nodes = new ArrayList<Node>();

		if(node.left != null){
			nodes.addAll( cLDR(node.left) );
		}
		nodes.add(node);
		if(node.right != null){
			nodes.addAll( cLDR(node.right) );
		}

		return nodes;
	}
}




测试代码如下:

package com.yc.test;

import com.yc.tree.RedBlackTree;

public class RedBlackTreeTest {
	public static void main(String[] args) {

		RedBlackTree<Integer> tree = new RedBlackTree<Integer>(1);
		tree.add(2);
		tree.add(3);
		tree.add(4);
		tree.add(5);
		tree.add(6);
		tree.add(7);
		tree.add(8);
		System.out.println( tree.breadthFirstSearch());
		
		tree.remove(6);
		System.out.println( tree.breadthFirstSearch());

	}
}


测试结果为:

[[data:4, color:true], [data:2, color:false], [data:6, color:false], [data:1, color:true], [data:3, color:true], [data:5, color:true], [data:7, color:true], [data:8, color:false]]
[[data:4, color:true], [data:2, color:false], [data:7, color:false], [data:1, color:true], [data:3, color:true], [data:5, color:true], [data:8, color:true]]

参考书籍:《疯狂Java程序员的基本修养》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值