数据结构(Java实现)-图解红黑树(R-B-Tree)

1、红黑树的基本介绍

红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树

红黑树满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。
红黑树的特性:

  • 1、每个节点上都有存储位表示节点的颜色,颜色是红(Red)或黑(Black)。
  • 2、根节点是黑色。
  • 3、如果一个节点是红色的,则它的子节点必须是黑色的。
  • 4、.从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)
  • 5、每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
    关于它的特性,需要注意的是:
    第一,特性5中的叶子节点,是只为空(NIL或null)的节点。
    第二,特性4,确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对接近平衡的二叉树

在这里插入图片描述

2、红黑树的创建

红黑树的创建是通过一个个节点的循环插入实现的。所以红黑树的创建其实就是红黑树节点的插入操作。

package com.zhukun;
class RBNode<T extends Comparable<T>> {
    private static final boolean RED = false;
    private static final boolean BLACK = true;
	boolean color;//颜色
    T key;//关键值
    RBNode<T> left;//左子节点
    RBNode<T> right;//右子节点
    RBNode<T> parent;//父节点
     
    public RBNode(boolean color,T key,RBNode<T> parent,RBNode<T> left,RBNode<T> right){
        this.color = color;
        this.key = key;
        this.parent = parent;
        this.left = left;
        this.right = right;
    }
     
    //获得节点的关键值
    public T getKey(){
        return key;
    }
    //打印节点的关键值和颜色信息
    public String toString(){
        return ""+key+(this.color ==RED ? "R":"B");
    }
}

class RBTree<T extends Comparable<T>> {
	private RBNode<T> root; //根节点
	private static final boolean RED = false; //定义红黑树标志
	private static final boolean BLACK = true;
	public RBTree() {
		root = null;
	}	
	public RBNode<T> parentOf(RBNode<T> node) { //获得父节点
		return node != null? node.parent : null;
	}
	public void setParent(RBNode<T> node, RBNode<T> parent) { //设置父节点
		if(node != null) 
			node.parent = parent;
	}
	public boolean colorOf(RBNode<T> node) { //获得节点的颜色
		return node != null? node.color : BLACK;
	}	
	public boolean isRed(RBNode<T> node) { //判断节点的颜色
		return (node != null)&&(node.color == RED)? true : false;
	}	
	public boolean isBlack(RBNode<T> node) {
		return !isRed(node);
	}	
	public void setRed(RBNode<T> node) { //设置节点的颜色
		if(node != null) 
			node.color = RED;
	}	
	public void setBlack(RBNode<T> node) {
		if(node != null) {
			node.color = BLACK;
		}
	} 
	public void setColor(RBNode<T> node, boolean color) {
		if(node != null)
			node.color = color;
	} 
	/***************** 前序遍历红黑树 *********************/
	public void preOrder() {
		preOrder(root);
	}
 
	private void preOrder(RBNode<T> tree) {
		if(tree != null) {
			System.out.print(tree.key + " ");
			preOrder(tree.left);
			preOrder(tree.right);
		}
	}
	/*********************** 向红黑树中插入节点 **********************/
	public void insert(T key) {
		RBNode<T> node = new RBNode<T>(RED, key, null, null, null);
		if(node != null) 
			insert(node);
	}	
	//将节点插入到红黑树中,这个过程与二叉搜索树是一样的
	private void insert(RBNode<T> node) {
		RBNode<T> current = null; //表示最后node的父节点
		RBNode<T> x = this.root; //用来向下搜索用的
		
		//1. 找到插入的位置
		while(x != null) {
			current = x;
			int cmp = node.key.compareTo(x.key);
			if(cmp < 0) 
				x = x.left;
			else
				x = x.right;
		}
		node.parent = current; //找到了位置,将当前current作为node的父节点
		
		//2. 接下来判断node是插在左子节点还是右子节点
		if(current != null) {
			int cmp = node.key.compareTo(current.key);
			if(cmp < 0)
				current.left = node;
			else
				current.right = node;
		} else {
			this.root = node;
		}		
	}
	public void print() {
		if(root != null) {
			print(root, root.key, 0);
		}
	}
	/******************* 打印红黑树 *********************/
	/*
	 * key---节点的键值
	 * direction--- 0:表示该节点是根节点
	 *              1:表示该节点是它的父节点的左子节点
	 *              2:表示该节点是它的父节点的右子节点
	 */
	private void print(RBNode<T> tree, T key, int direction) {
		if(tree != null) {
			if(0 == direction) 
				System.out.printf("%2d(B) is root\n", tree.key);
			else
				System.out.printf("%2d(%s) is %2d's %6s child\n", 
						tree.key, isRed(tree)?"R":"B", key, direction == 1?"right":"left");
			print(tree.left, tree.key, -1);
			print(tree.right, tree.key, 1);
		}
	}
}
class test { 
	public static void main(String[] args) 
	{ 
		RBTree<Integer> tree = new RBTree<Integer>();
		int a[] = {11, 2, 1, 7, 5,4, 8, 14, 15};
		System.out.printf("== 原始数据: ");
		for(int i=0;i<a.length;i++)
		{
			System.out.print(a[i]+"  ");
		}
		System.out.println();
		System.out.println("构造红黑树!");
		for(int i=0; i<a.length; i++) {
            tree.insert(a[i]);
         }
         System.out.printf("== 前序遍历: ");
         tree.preOrder();
         System.out.println();
         System.out.println("树的详细信息:");
         tree.print();
	}
}
== 原始数据: 11  2  1  7  5  4  8  14  15  
构造红黑树!
== 前序遍历: 11 2 1 7 5 4 8 14 15 
树的详细信息:
11(B) is root
 2(R) is 11's   left child
 1(R) is  2's   left child
 7(R) is  2's  right child
 5(R) is  7's   left child
 4(R) is  5's   left child
 8(R) is  7's  right child
14(R) is 11's  right child
15(R) is 14's  right child

在这里插入图片描述

我们发现在红-黑树中插入的节点都是红色的
注意:这不是偶然的
因为插入一个红色节点比插入一个黑色节点违背红-黑规则的可能性更小。
原因是:插入黑色节点总会改变黑色高度(违背规则4),但是插入红色节点只有一半的机会会违背规则3。另外违背规则3比违背规则4要更容易修正。当插入一个新的节点时,可能会破坏这种平衡性,那么红-黑树是如何修正的呢?

3、红黑树平衡性的修正

红-黑树主要通过三种方式对平衡进行修正,改变节点颜色、左旋和右旋。

3.1 变色

新插入的节点为15,一般新插入颜色都为红色,那么我们发现直接插入会违反规则3,改为黑色却发现违反规则4。这时候我们将其父节点颜色改为黑色,父节点的兄弟节点颜色也改为黑色。通常其祖父节点50颜色会由黑色变为红色,但是由于50是根节点,所以我们这里不能改变根节点颜色。
在这里插入图片描述
注意: 我们改变颜色也是为了帮助我们判断何时执行什么旋转,而旋转是为了保证树的平衡。光改变节点颜色是不能起到任何作用的,旋转才是关键的操作,在新增节点或者删除节点之后,可能会破坏二叉树的平衡,那么何时执行旋转以及执行什么旋转,这是我们需要重点关注的。

3.2右旋

首先要说明的是节点本身是不会旋转的,旋转改变的是节点之间的关系,选择一个节点作为旋转的顶端,如果做一次右旋,这个顶端节点会向下和向右移动到它右子节点的位置,它的左子节点会上移到它原来的位置。右旋的顶端节点必须要有左子节点。
在这里插入图片描述

3.3左旋

左旋的顶端节点必须要有右子节点。
在这里插入图片描述
(左旋和右旋在平衡二叉树中有具体介绍)

3.4插入时的修正

如果是第一次插入,由于原树为空,所以只会违反红-黑树的规则2,所以只要把根节点涂黑即可;如果插入节点的父节点是黑色的,那不会违背红-黑树的规则,什么也不需要做;但是遇到如下三种情况时,我们就要开始变色和旋转了:

  • 1.插入节点的父节点和其叔叔节点(祖父节点的另一个子节点)均为红色的;
  • 2.插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的右子节点;
  • 3.插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的左子节点。

下面我们先挨个分析这三种情况都需要如何操作,然后再给出实现代码。
对于情况1:插入节点的父节点和其叔叔节点(祖父节点的另一个子节点)均为红色的。此时,肯定存在祖父节点,但是不知道父节点是其左子节点还是右子节点,但是由于对称性,我们只要讨论出一边的情况,另一种情况自然也与之对应。这里考虑父节点是祖父节点的左子节点的情况,如图所示:
在这里插入图片描述
对于这种情况,我们要做的操作有:将当前节点(4)的父节点(5)和叔叔节点(8)涂黑,将祖父节点(7)涂红,变成下图所示的情况。再将当前节点指向其祖父节点,再次从新的当前节点开始算法(具体等下看下面的程序)。这样下图就变成了情况2了。
在这里插入图片描述
对于情况2:插入节点的父节点是红色的,叔叔节点是黑色的,且插入节点是其父节点的右子节点。我们要做的操作有:将当前节点(7)的父节点(2)作为新的节点,以新的当前节点为支点做左旋操作。完成后如下图所示,这样就变成情况3了
在这里插入图片描述
对于情况3:插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的左子节点。我们要做的操作有:将当前节点的父节点(7)涂黑,将祖父节点(11)涂红,在祖父节点为支点做右旋操作。最后把根节点涂黑,整个红-黑树重新恢复了平衡,如下图所示。至此,插入修正操作完成!
在这里插入图片描述
总结:我们可以看出,如果是从情况1开始发生的,必然会走完情况2和3,也就是说这是一整个流程,当然咯,实际中可能不一定会从情况1发生,如果从情况2开始发生,那再走个情况3即可完成调整,如果直接只要调整情况3,那么前两种情况均不需要调整了。故变色和旋转之间的先后关系可以表示为:变色->左旋->右旋

修正后插入代码实现:

package com.zhukun;
class RBNode<T extends Comparable<T>> {
    private static final boolean RED = false;
    private static final boolean BLACK = true;
	boolean color;//颜色
    T key;//关键值
    RBNode<T> left;//左子节点
    RBNode<T> right;//右子节点
    RBNode<T> parent;//父节点
     
    public RBNode(boolean color,T key,RBNode<T> parent,RBNode<T> left,RBNode<T> right){
        this.color = color;
        this.key = key;
        this.parent = parent;
        this.left = left;
        this.right = right;
    }
     
    //获得节点的关键值
    public T getKey(){
        return key;
    }
    //打印节点的关键值和颜色信息
    public String toString(){
        return ""+key+(this.color ==RED ? "R":"B");
    }
}

class RBTree<T extends Comparable<T>> {
	private RBNode<T> root; //根节点
	private static final boolean RED = false; //定义红黑树标志
	private static final boolean BLACK = true;
	public RBTree() {
		root = null;
	}	
	public RBNode<T> parentOf(RBNode<T> node) { //获得父节点
		return node != null? node.parent : null;
	}
	public void setParent(RBNode<T> node, RBNode<T> parent) { //设置父节点
		if(node != null) 
			node.parent = parent;
	}
	public boolean colorOf(RBNode<T> node) { //获得节点的颜色
		return node != null? node.color : BLACK;
	}	
	public boolean isRed(RBNode<T> node) { //判断节点的颜色
		return (node != null)&&(node.color == RED)? true : false;
	}	
	public boolean isBlack(RBNode<T> node) {
		return !isRed(node);
	}	
	public void setRed(RBNode<T> node) { //设置节点的颜色
		if(node != null) 
			node.color = RED;
	}	
	public void setBlack(RBNode<T> node) {
		if(node != null) {
			node.color = BLACK;
		}
	} 
	public void setColor(RBNode<T> node, boolean color) {
		if(node != null)
			node.color = color;
	} 
	/***************** 前序遍历红黑树 *********************/
	public void preOrder() {
		preOrder(root);
	}
 
	private void preOrder(RBNode<T> tree) {
		if(tree != null) {
			System.out.print(tree.key + " ");
			preOrder(tree.left);
			preOrder(tree.right);
		}
	}
	/**************** 查找最小节点的值  **********************/
	public T minValue() {
		RBNode<T> node = minNode(root);
		if(node != null)
			return node.key;
		return null;
	}
 
	private RBNode<T> minNode(RBNode<T> tree) {
		if(tree == null)
			return null;
		while(tree.left != null) {
			tree = tree.left;
		}
		return tree;
	}
	
	/******************** 查找最大节点的值 *******************/
	public T maxValue() {
		RBNode<T> node = maxNode(root);
		if(node != null)
			return node.key;
		return null;
	}
 
	private RBNode<T> maxNode(RBNode<T> tree) {
		if(tree == null)
			return null;
		while(tree.right != null)
			tree = tree.right;
		return tree;
	}

	/********* 查找节点x的后继节点,即大于节点x的最小节点 ***********/
	public RBNode<T> successor(RBNode<T> x) {
		//如果x有右子节点,那么后继节点为“以右子节点为根的子树的最小节点”
		if(x.right != null) 
			return minNode(x.right);
		//如果x没有右子节点,会出现以下两种情况:
		//1. x是其父节点的左子节点,则x的后继节点为它的父节点
		//2. x是其父节点的右子节点,则先查找x的父节点p,然后对p再次进行这两个条件的判断
		RBNode<T> p = x.parent;
		while((p != null) && (x == p.right)) { //对应情况2
			x = p;
			p = x.parent;
		}
		return p; //对应情况1
		
	}
	
	/********* 查找节点x的前驱节点,即小于节点x的最大节点 ************/
	public RBNode<T> predecessor(RBNode<T> x) {
		//如果x有左子节点,那么前驱结点为“左子节点为根的子树的最大节点”
		if(x.left != null) 
			return maxNode(x.left);
		//如果x没有左子节点,会出现以下两种情况:
		//1. x是其父节点的右子节点,则x的前驱节点是它的父节点
		//2. x是其父节点的左子节点,则先查找x的父节点p,然后对p再次进行这两个条件的判断
		RBNode<T> p = x.parent;
		while((p != null) && (x == p.left)) { //对应情况2
			x = p;
			p = x.parent;
		}
		return p; //对应情况1
	}

	/*************对红黑树节点x进行左旋操作 ******************/
	/*
	 * 左旋示意图:对节点x进行左旋
	 *     p                       p
	 *    /                       /
	 *   x                       y
	 *  / \                     / \
	 * lx  y      ----->       x  ry
	 *    / \                 / \
	 *   ly ry               lx ly
	 * 左旋做了三件事:
	 * 1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
	 * 2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
	 * 3. 将y的左子节点设为x,将x的父节点设为y
	 */
	private void leftRotate(RBNode<T> x) {
		//1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
		RBNode<T> y = x.right;
		x.right = y.left;
		
		if(y.left != null) 
			y.left.parent = x;
		
		//2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
		y.parent = x.parent;
		
		if(x.parent == null) {
			this.root = y; //如果x的父节点为空,则将y设为父节点
		} else {
			if(x == x.parent.left) //如果x是左子节点
				x.parent.left = y; //则也将y设为左子节点
			else
				x.parent.right = y;//否则将y设为右子节点
		}
		
		//3. 将y的左子节点设为x,将x的父节点设为y
		y.left = x;
		x.parent = y;		
	}
	
	/*************对红黑树节点y进行右旋操作 ******************/
	/*
	 * 左旋示意图:对节点y进行右旋
	 *        p                   p
	 *       /                   /
	 *      y                   x
	 *     / \                 / \
	 *    x  ry   ----->      lx  y
	 *   / \                     / \
	 * lx  rx                   rx ry
	 * 右旋做了三件事:
	 * 1. 将x的右子节点赋给y的左子节点,并将y赋给x右子节点的父节点(x右子节点非空时)
	 * 2. 将y的父节点p(非空时)赋给x的父节点,同时更新p的子节点为x(左或右)
	 * 3. 将x的右子节点设为y,将y的父节点设为x
	 */
	private void rightRotate(RBNode<T> y) {
		//1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
		RBNode<T> x = y.left;
		y.left = x.right;
		
		if(x.right != null) 
			x.right.parent = y;
		
		//2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
		x.parent = y.parent;
		
		if(y.parent == null) {
			this.root = x; //如果x的父节点为空,则将y设为父节点
		} else {
			if(y == y.parent.right) //如果x是左子节点
				y.parent.right = x; //则也将y设为左子节点
			else
				y.parent.left = x;//否则将y设为右子节点
		}
		
		//3. 将y的左子节点设为x,将x的父节点设为y
		x.right = y;
		y.parent = x;		
	}

	/*********************** 向红黑树中插入节点 **********************/
	public void insert(T key) {
		RBNode<T> node = new RBNode<T>(RED, key, null, null, null);
		if(node != null) 
			insert(node);
	}	
	//将节点插入到红黑树中,这个过程与二叉搜索树是一样的
	private void insert(RBNode<T> node) {
		RBNode<T> current = null; //表示最后node的父节点
		RBNode<T> x = this.root; //用来向下搜索用的
		
		//1. 找到插入的位置
		while(x != null) {
			current = x;
			int cmp = node.key.compareTo(x.key);
			if(cmp < 0) 
				x = x.left;
			else
				x = x.right;
		}
		node.parent = current; //找到了位置,将当前current作为node的父节点
		
		//2. 接下来判断node是插在左子节点还是右子节点
		if(current != null) {
			int cmp = node.key.compareTo(current.key);
			if(cmp < 0)
				current.left = node;
			else
				current.right = node;
		} else {
			this.root = node;
		}		
		//3. 将它重新修整为一颗红黑树
		insertFixUp(node);
	}
	/*
	 * 红黑树插入修正函数
	 * 在向红黑树中插入节点之后失去平衡,需要通过旋转重新达到平衡
	 */
	private void insertFixUp(RBNode<T> node) {
		RBNode<T> parent, gparent; //定义父节点和祖父节点
		
		//需要修整的条件:父节点存在,且父节点的颜色是红色
		while(((parent = parentOf(node)) != null) && isRed(parent)) {
			gparent = parentOf(parent);//获得祖父节点
			
			//若父节点是祖父节点的左子节点,下面else与其相反
			if(parent == gparent.left) {				
				RBNode<T> uncle = gparent.right; //获得叔叔节点
				
				//case1: 叔叔节点也是红色
				if(uncle != null && isRed(uncle)) {
					setBlack(parent); //把父节点和叔叔节点涂黑
					setBlack(uncle);
					setRed(gparent); //把祖父节点涂红
					node = gparent; //将位置放到祖父节点处
					continue; //继续while,重新判断
				}
				
				//case2: 叔叔节点是黑色,且当前节点是右子节点
				if(node == parent.right) {
					leftRotate(parent); //从父节点处左旋
					RBNode<T> tmp = parent; //然后将父节点和自己调换一下,为下面右旋做准备
					parent = node;
					node = tmp;
				}
				
				//case3: 叔叔节点是黑色,且当前节点是左子节点
				setBlack(parent);
				setRed(gparent);
				rightRotate(gparent);
			} else { //若父节点是祖父节点的右子节点,与上面的完全相反,本质一样的
				RBNode<T> uncle = gparent.left;
				
				//case1: 叔叔节点也是红色
				if(uncle != null & isRed(uncle)) {
					setBlack(parent);
					setBlack(uncle);
					setRed(gparent);
					node = gparent;
					continue;
				}
				
				//case2: 叔叔节点是黑色的,且当前节点是左子节点
				if(node == parent.left) {
					rightRotate(parent);
					RBNode<T> tmp = parent;
					parent = node;
					node = tmp;
				}
				
				//case3: 叔叔节点是黑色的,且当前节点是右子节点
				setBlack(parent);
				setRed(gparent);
				leftRotate(gparent);
			}
		}
		
		//将根节点设置为黑色
		setBlack(this.root);
	}
	public void print() {
		if(root != null) {
			print(root, root.key, 0);
		}
	}
	/******************* 打印红黑树 *********************/
	/*
	 * key---节点的键值
	 * direction--- 0:表示该节点是根节点
	 *              1:表示该节点是它的父节点的左子节点
	 *              2:表示该节点是它的父节点的右子节点
	 */
	private void print(RBNode<T> tree, T key, int direction) {
		if(tree != null) {
			if(0 == direction) 
				System.out.printf("%2d(B) is root\n", tree.key);
			else
				System.out.printf("%2d(%s) is %2d's %6s child\n", 
						tree.key, isRed(tree)?"R":"B", key, direction == 1?"right":"left");
			print(tree.left, tree.key, -1);
			print(tree.right, tree.key, 1);
		}
	}
}
class test { 
	public static void main(String[] args) 
	{ 
		RBTree<Integer> tree = new RBTree<Integer>();
		int a[] = {11, 2, 1, 7, 5,4, 8, 14, 15};
		System.out.printf("== 原始数据: ");
		for(int i=0;i<a.length;i++)
		{
			System.out.print(a[i]+"  ");
		}
		System.out.println();
		System.out.println("构造红黑树!");
		for(int i=0; i<a.length; i++) {
            tree.insert(a[i]);
         }
         System.out.printf("== 前序遍历: ");
         tree.preOrder();
         System.out.println();
         System.out.println("树的详细信息:");
         tree.print();
	}
}
== 原始数据: 11  2  1  7  5  4  8  14  15  
构造红黑树!
== 前序遍历: 7 2 1 5 4 11 8 14 15 
树的详细信息:
 7(B) is root
 2(R) is  7's   left child
 1(B) is  2's   left child
 5(B) is  2's  right child
 4(R) is  5's   left child
11(R) is  7's  right child
 8(B) is 11's   left child
14(B) is 11's  right child
15(R) is 14's  right child

在这里插入图片描述

3.5删除时的修正

上面探讨完了红-黑树的插入操作,接下来讨论删除,红-黑树的删除和二叉查找树的删除是一样的,只不过删除后多了个平衡的修复而已。
删除时的修正

	private void removeFixUp(RBNode<T> node, RBNode<T> parent) {
		RBNode<T> other;
		
		while((node == null || isBlack(node)) && (node != this.root)) {
			if(parent.left == node) { //node是左子节点,下面else与这里的刚好相反
				other = parent.right; //node的兄弟节点
				if(isRed(other)) { //case1: node的兄弟节点other是红色的
					setBlack(other);
					setRed(parent);
					leftRotate(parent);
					other = parent.right;
				}
				
				//case2: node的兄弟节点other是黑色的,且other的两个子节点也都是黑色的
				if((other.left == null || isBlack(other.left)) && 
						(other.right == null || isBlack(other.right))) {
					setRed(other);
					node = parent;
					parent = parentOf(node);
				} else {
					//case3: node的兄弟节点other是黑色的,且other的左子节点是红色,右子节点是黑色
					if(other.right == null || isBlack(other.right)) {
						setBlack(other.left);
						setRed(other);
						rightRotate(other);
						other = parent.right;
					}
					
					//case4: node的兄弟节点other是黑色的,且other的右子节点是红色,左子节点任意颜色
					setColor(other, colorOf(parent));
					setBlack(parent);
					setBlack(other.right);
					leftRotate(parent);
					node = this.root;
					break;
				}
			} else { //与上面的对称
				other = parent.left;
				
	            if (isRed(other)) {
	                // Case 1: node的兄弟other是红色的  
	                setBlack(other);
	                setRed(parent);
	                rightRotate(parent);
	                other = parent.left;
	            }
 
	            if ((other.left==null || isBlack(other.left)) &&
	                (other.right==null || isBlack(other.right))) {
	                // Case 2: node的兄弟other是黑色,且other的俩个子节点都是黑色的  
	                setRed(other);
	                node = parent;
	                parent = parentOf(node);
	            } else {
 
	                if (other.left==null || isBlack(other.left)) {
	                    // Case 3: node的兄弟other是黑色的,并且other的左子节点是红色,右子节点为黑色。  
	                    setBlack(other.right);
	                    setRed(other);
	                    leftRotate(other);
	                    other = parent.left;
	                }
 
	                // Case 4: node的兄弟other是黑色的;并且other的左子节点是红色的,右子节点任意颜色
	                setColor(other, colorOf(parent));
	                setBlack(parent);
	                setBlack(other.left);
	                rightRotate(parent);
	                node = this.root;
	                break;
	            }
			}
		}
		if (node!=null)
	        setBlack(node);
	}

比如删除11节点后:
在这里插入图片描述

4、红黑树的效率

红黑树的查找、插入和删除时间复杂度都为O(log2N),额外的开销是每个节点的存储空间都稍微增加了一点,因为一个存储红黑树节点的颜色变量。插入和删除的时间要增加一个常数因子,因为要进行旋转,平均一次插入大约需要一次旋转,因此插入的时间复杂度还是O(log2N),(时间复杂度的计算要省略常数),但实际上比普通的二叉树是要慢的。
大多数应用中,查找的次数比插入和删除的次数多,所以应用红黑树取代普通的二叉搜索树总体上不会有太多的时间开销。而且红黑树的优点是对于有序数据的操作不会慢到O(N)的时间复杂度。

5、总代码:

package com.zhukun;
class RBNode<T extends Comparable<T>> {
    private static final boolean RED = false;
    private static final boolean BLACK = true;
	boolean color;//颜色
    T key;//关键值
    RBNode<T> left;//左子节点
    RBNode<T> right;//右子节点
    RBNode<T> parent;//父节点
     
    public RBNode(boolean color,T key,RBNode<T> parent,RBNode<T> left,RBNode<T> right){
        this.color = color;
        this.key = key;
        this.parent = parent;
        this.left = left;
        this.right = right;
    }
     
    //获得节点的关键值
    public T getKey(){
        return key;
    }
    //打印节点的关键值和颜色信息
    public String toString(){
        return ""+key+(this.color ==RED ? "R":"B");
    }
}

class RBTree<T extends Comparable<T>> {
	private RBNode<T> root; //根节点
	private static final boolean RED = false; //定义红黑树标志
	private static final boolean BLACK = true;
	public RBTree() {
		root = null;
	}	
	public RBNode<T> parentOf(RBNode<T> node) { //获得父节点
		return node != null? node.parent : null;
	}
	public void setParent(RBNode<T> node, RBNode<T> parent) { //设置父节点
		if(node != null) 
			node.parent = parent;
	}
	public boolean colorOf(RBNode<T> node) { //获得节点的颜色
		return node != null? node.color : BLACK;
	}	
	public boolean isRed(RBNode<T> node) { //判断节点的颜色
		return (node != null)&&(node.color == RED)? true : false;
	}	
	public boolean isBlack(RBNode<T> node) {
		return !isRed(node);
	}	
	public void setRed(RBNode<T> node) { //设置节点的颜色
		if(node != null) 
			node.color = RED;
	}	
	public void setBlack(RBNode<T> node) {
		if(node != null) {
			node.color = BLACK;
		}
	} 
	public void setColor(RBNode<T> node, boolean color) {
		if(node != null)
			node.color = color;
	} 
	/***************** 前序遍历红黑树 *********************/
	public void preOrder() {
		preOrder(root);
	}
 
	private void preOrder(RBNode<T> tree) {
		if(tree != null) {
			System.out.print(tree.key + " ");
			preOrder(tree.left);
			preOrder(tree.right);
		}
	}
	/**************** 查找最小节点的值  **********************/
	public T minValue() {
		RBNode<T> node = minNode(root);
		if(node != null)
			return node.key;
		return null;
	}
 
	private RBNode<T> minNode(RBNode<T> tree) {
		if(tree == null)
			return null;
		while(tree.left != null) {
			tree = tree.left;
		}
		return tree;
	}
	
	/******************** 查找最大节点的值 *******************/
	public T maxValue() {
		RBNode<T> node = maxNode(root);
		if(node != null)
			return node.key;
		return null;
	}
 
	private RBNode<T> maxNode(RBNode<T> tree) {
		if(tree == null)
			return null;
		while(tree.right != null)
			tree = tree.right;
		return tree;
	}

	/********* 查找节点x的后继节点,即大于节点x的最小节点 ***********/
	public RBNode<T> successor(RBNode<T> x) {
		//如果x有右子节点,那么后继节点为“以右子节点为根的子树的最小节点”
		if(x.right != null) 
			return minNode(x.right);
		//如果x没有右子节点,会出现以下两种情况:
		//1. x是其父节点的左子节点,则x的后继节点为它的父节点
		//2. x是其父节点的右子节点,则先查找x的父节点p,然后对p再次进行这两个条件的判断
		RBNode<T> p = x.parent;
		while((p != null) && (x == p.right)) { //对应情况2
			x = p;
			p = x.parent;
		}
		return p; //对应情况1
		
	}
	
	/********* 查找节点x的前驱节点,即小于节点x的最大节点 ************/
	public RBNode<T> predecessor(RBNode<T> x) {
		//如果x有左子节点,那么前驱结点为“左子节点为根的子树的最大节点”
		if(x.left != null) 
			return maxNode(x.left);
		//如果x没有左子节点,会出现以下两种情况:
		//1. x是其父节点的右子节点,则x的前驱节点是它的父节点
		//2. x是其父节点的左子节点,则先查找x的父节点p,然后对p再次进行这两个条件的判断
		RBNode<T> p = x.parent;
		while((p != null) && (x == p.left)) { //对应情况2
			x = p;
			p = x.parent;
		}
		return p; //对应情况1
	}

	/*************对红黑树节点x进行左旋操作 ******************/
	/*
	 * 左旋示意图:对节点x进行左旋
	 *     p                       p
	 *    /                       /
	 *   x                       y
	 *  / \                     / \
	 * lx  y      ----->       x  ry
	 *    / \                 / \
	 *   ly ry               lx ly
	 * 左旋做了三件事:
	 * 1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
	 * 2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
	 * 3. 将y的左子节点设为x,将x的父节点设为y
	 */
	private void leftRotate(RBNode<T> x) {
		//1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
		RBNode<T> y = x.right;
		x.right = y.left;
		
		if(y.left != null) 
			y.left.parent = x;
		
		//2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
		y.parent = x.parent;
		
		if(x.parent == null) {
			this.root = y; //如果x的父节点为空,则将y设为父节点
		} else {
			if(x == x.parent.left) //如果x是左子节点
				x.parent.left = y; //则也将y设为左子节点
			else
				x.parent.right = y;//否则将y设为右子节点
		}
		
		//3. 将y的左子节点设为x,将x的父节点设为y
		y.left = x;
		x.parent = y;		
	}
	
	/*************对红黑树节点y进行右旋操作 ******************/
	/*
	 * 左旋示意图:对节点y进行右旋
	 *        p                   p
	 *       /                   /
	 *      y                   x
	 *     / \                 / \
	 *    x  ry   ----->      lx  y
	 *   / \                     / \
	 * lx  rx                   rx ry
	 * 右旋做了三件事:
	 * 1. 将x的右子节点赋给y的左子节点,并将y赋给x右子节点的父节点(x右子节点非空时)
	 * 2. 将y的父节点p(非空时)赋给x的父节点,同时更新p的子节点为x(左或右)
	 * 3. 将x的右子节点设为y,将y的父节点设为x
	 */
	private void rightRotate(RBNode<T> y) {
		//1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
		RBNode<T> x = y.left;
		y.left = x.right;
		
		if(x.right != null) 
			x.right.parent = y;
		
		//2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
		x.parent = y.parent;
		
		if(y.parent == null) {
			this.root = x; //如果x的父节点为空,则将y设为父节点
		} else {
			if(y == y.parent.right) //如果x是左子节点
				y.parent.right = x; //则也将y设为左子节点
			else
				y.parent.left = x;//否则将y设为右子节点
		}
		
		//3. 将y的左子节点设为x,将x的父节点设为y
		x.right = y;
		y.parent = x;		
	}

	/*********************** 向红黑树中插入节点 **********************/
	public void insert(T key) {
		RBNode<T> node = new RBNode<T>(RED, key, null, null, null);
		if(node != null) 
			insert(node);
	}	
	//将节点插入到红黑树中,这个过程与二叉搜索树是一样的
	private void insert(RBNode<T> node) {
		RBNode<T> current = null; //表示最后node的父节点
		RBNode<T> x = this.root; //用来向下搜索用的
		
		//1. 找到插入的位置
		while(x != null) {
			current = x;
			int cmp = node.key.compareTo(x.key);
			if(cmp < 0) 
				x = x.left;
			else
				x = x.right;
		}
		node.parent = current; //找到了位置,将当前current作为node的父节点
		
		//2. 接下来判断node是插在左子节点还是右子节点
		if(current != null) {
			int cmp = node.key.compareTo(current.key);
			if(cmp < 0)
				current.left = node;
			else
				current.right = node;
		} else {
			this.root = node;
		}		
		//3. 将它重新修整为一颗红黑树
		insertFixUp(node);
	}
	/*
	 * 红黑树插入修正函数
	 * 在向红黑树中插入节点之后失去平衡,需要通过旋转重新达到平衡
	 */
	private void insertFixUp(RBNode<T> node) {
		RBNode<T> parent, gparent; //定义父节点和祖父节点
		
		//需要修整的条件:父节点存在,且父节点的颜色是红色
		while(((parent = parentOf(node)) != null) && isRed(parent)) {
			gparent = parentOf(parent);//获得祖父节点
			
			//若父节点是祖父节点的左子节点,下面else与其相反
			if(parent == gparent.left) {				
				RBNode<T> uncle = gparent.right; //获得叔叔节点
				
				//case1: 叔叔节点也是红色
				if(uncle != null && isRed(uncle)) {
					setBlack(parent); //把父节点和叔叔节点涂黑
					setBlack(uncle);
					setRed(gparent); //把祖父节点涂红
					node = gparent; //将位置放到祖父节点处
					continue; //继续while,重新判断
				}
				
				//case2: 叔叔节点是黑色,且当前节点是右子节点
				if(node == parent.right) {
					leftRotate(parent); //从父节点处左旋
					RBNode<T> tmp = parent; //然后将父节点和自己调换一下,为下面右旋做准备
					parent = node;
					node = tmp;
				}
				
				//case3: 叔叔节点是黑色,且当前节点是左子节点
				setBlack(parent);
				setRed(gparent);
				rightRotate(gparent);
			} else { //若父节点是祖父节点的右子节点,与上面的完全相反,本质一样的
				RBNode<T> uncle = gparent.left;
				
				//case1: 叔叔节点也是红色
				if(uncle != null & isRed(uncle)) {
					setBlack(parent);
					setBlack(uncle);
					setRed(gparent);
					node = gparent;
					continue;
				}
				
				//case2: 叔叔节点是黑色的,且当前节点是左子节点
				if(node == parent.left) {
					rightRotate(parent);
					RBNode<T> tmp = parent;
					parent = node;
					node = tmp;
				}
				
				//case3: 叔叔节点是黑色的,且当前节点是右子节点
				setBlack(parent);
				setRed(gparent);
				leftRotate(gparent);
			}
		}
		
		//将根节点设置为黑色
		setBlack(this.root);
	}
	public void print() {
		if(root != null) {
			print(root, root.key, 0);
		}
	}
	
	/**************** 查找红黑树中键值为key的节点 ***************/
	public RBNode<T> search(T key) {
		return search(root, key);
//		return search2(root, key); //使用递归的方法,本质一样的
	}
 
	private RBNode<T> search(RBNode<T> x, T key) {
		while(x != null) {
			int cmp = key.compareTo(x.key);
			if(cmp < 0) 
				x = x.left;
			else if(cmp > 0) 
				x = x.right;
			else 
				return x;
		}
		return x;
	}
	//使用递归
	private RBNode<T> search2(RBNode<T> x, T key) {
		if(x == null)
			return x;
		int cmp = key.compareTo(x.key);
		if(cmp < 0)
			return search2(x.left, key);
		else if(cmp > 0) 
			return search2(x.right, key);
		else
			return x;
	}

	/*********************** 删除红黑树中的节点 **********************/
	public void remove(T key) {
		RBNode<T> node;
		if((node = search(root, key)) != null)
			remove(node);
	}
	
	private void remove(RBNode<T> node) {
		RBNode<T> child, parent;
		boolean color;
		
		//1. 被删除的节点“左右子节点都不为空”的情况
		if((node.left != null) && (node.right != null)) {
			//先找到被删除节点的后继节点,用它来取代被删除节点的位置
			RBNode<T> replace = node;
			//  1). 获取后继节点
			replace = replace.right;
			while(replace.left != null) 
				replace = replace.left;
			
			//  2). 处理“后继节点”和“被删除节点的父节点”之间的关系
			if(parentOf(node) != null) { //要删除的节点不是根节点
				if(node == parentOf(node).left) 
					parentOf(node).left = replace;
				else
					parentOf(node).right = replace;
			} else { //否则
				this.root = replace;
			}
			
			//  3). 处理“后继节点的子节点”和“被删除节点的子节点”之间的关系
			child = replace.right; //后继节点肯定不存在左子节点!
			parent = parentOf(replace);
			color = colorOf(replace);//保存后继节点的颜色
			if(parent == node) { //后继节点是被删除节点的子节点
				parent = replace;
			} else { //否则
				if(child != null) 
					setParent(child, parent);
				parent.left = child;
				replace.right = node.right;
				setParent(node.right, replace);
			}
			replace.parent = node.parent;
			replace.color = node.color; //保持原来位置的颜色
			replace.left = node.left;
			node.left.parent = replace;
			
			if(color == BLACK) { //4. 如果移走的后继节点颜色是黑色,重新修整红黑树
				removeFixUp(child, parent);//将后继节点的child和parent传进去
			}
			node = null;
			return;
		}
	}
	//node表示待修正的节点,即后继节点的子节点(因为后继节点被挪到删除节点的位置去了)
	private void removeFixUp(RBNode<T> node, RBNode<T> parent) {
		RBNode<T> other;
		
		while((node == null || isBlack(node)) && (node != this.root)) {
			if(parent.left == node) { //node是左子节点,下面else与这里的刚好相反
				other = parent.right; //node的兄弟节点
				if(isRed(other)) { //case1: node的兄弟节点other是红色的
					setBlack(other);
					setRed(parent);
					leftRotate(parent);
					other = parent.right;
				}
				
				//case2: node的兄弟节点other是黑色的,且other的两个子节点也都是黑色的
				if((other.left == null || isBlack(other.left)) && 
						(other.right == null || isBlack(other.right))) {
					setRed(other);
					node = parent;
					parent = parentOf(node);
				} else {
					//case3: node的兄弟节点other是黑色的,且other的左子节点是红色,右子节点是黑色
					if(other.right == null || isBlack(other.right)) {
						setBlack(other.left);
						setRed(other);
						rightRotate(other);
						other = parent.right;
					}
					
					//case4: node的兄弟节点other是黑色的,且other的右子节点是红色,左子节点任意颜色
					setColor(other, colorOf(parent));
					setBlack(parent);
					setBlack(other.right);
					leftRotate(parent);
					node = this.root;
					break;
				}
			} else { //与上面的对称
				other = parent.left;
				
	            if (isRed(other)) {
	                // Case 1: node的兄弟other是红色的  
	                setBlack(other);
	                setRed(parent);
	                rightRotate(parent);
	                other = parent.left;
	            }
 
	            if ((other.left==null || isBlack(other.left)) &&
	                (other.right==null || isBlack(other.right))) {
	                // Case 2: node的兄弟other是黑色,且other的俩个子节点都是黑色的  
	                setRed(other);
	                node = parent;
	                parent = parentOf(node);
	            } else {
 
	                if (other.left==null || isBlack(other.left)) {
	                    // Case 3: node的兄弟other是黑色的,并且other的左子节点是红色,右子节点为黑色。  
	                    setBlack(other.right);
	                    setRed(other);
	                    leftRotate(other);
	                    other = parent.left;
	                }
 
	                // Case 4: node的兄弟other是黑色的;并且other的左子节点是红色的,右子节点任意颜色
	                setColor(other, colorOf(parent));
	                setBlack(parent);
	                setBlack(other.left);
	                rightRotate(parent);
	                node = this.root;
	                break;
	            }
			}
		}
		if (node!=null)
	        setBlack(node);
	}

	/******************* 打印红黑树 *********************/
	/*
	 * key---节点的键值
	 * direction--- 0:表示该节点是根节点
	 *              1:表示该节点是它的父节点的左子节点
	 *              2:表示该节点是它的父节点的右子节点
	 */
	private void print(RBNode<T> tree, T key, int direction) {
		if(tree != null) {
			if(0 == direction) 
				System.out.printf("%2d(B) is root\n", tree.key);
			else
				System.out.printf("%2d(%s) is %2d's %6s child\n", 
						tree.key, isRed(tree)?"R":"B", key, direction == 1?"right":"left");
			print(tree.left, tree.key, -1);
			print(tree.right, tree.key, 1);
		}
	}
	/****************** 销毁红黑树 *********************/
	public void clear() {
		destroy(root);
		root = null;
	}
 
	private void destroy(RBNode<T> tree) {
		if(tree == null) 
			return;
		if(tree.left != null) 
			destroy(tree.left);
		if(tree.right != null) 
			destroy(tree.right);
		tree = null;
	}
}
class test { 
	public static void main(String[] args) 
	{ 
		RBTree<Integer> tree = new RBTree<Integer>();
		int a[] = {11, 2, 1, 7, 5,4, 8, 14, 15};
		System.out.printf("== 原始数据: ");
		for(int i=0;i<a.length;i++)
		{
			System.out.print(a[i]+"  ");
		}
		System.out.println();
		System.out.println("构造红黑树!");
		for(int i=0; i<a.length; i++) {
            tree.insert(a[i]);
         }
         System.out.printf("== 前序遍历: ");
         tree.preOrder();
         System.out.println();
         System.out.println("树的详细信息:");
         tree.print();
         System.out.printf("== 最小值: %s\n", tree.minValue());
         System.out.printf("== 最大值: %s\n", tree.maxValue());
         System.out.println("删除节点11后:");
         tree.remove(11);
         tree.print();
         tree.clear();
	}
}
== 原始数据: 11  2  1  7  5  4  8  14  15  
构造红黑树!
== 前序遍历: 7 2 1 5 4 11 8 14 15 
树的详细信息:
 7(B) is root
 2(R) is  7's   left child
 1(B) is  2's   left child
 5(B) is  2's  right child
 4(R) is  5's   left child
11(R) is  7's  right child
 8(B) is 11's   left child
14(B) is 11's  right child
15(R) is 14's  right child
== 最小值: 1
== 最大值: 15
删除节点11后:
 7(B) is root
 2(R) is  7's   left child
 1(B) is  2's   left child
 5(B) is  2's  right child
 4(R) is  5's   left child
14(R) is  7's  right child
 8(B) is 14's   left child
15(B) is 14's  right child

参考博客:https://blog.csdn.net/eson_15/article/details/51144079

©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页