一、背景
二叉搜索树具体较高的运行效率,其查找的时间复杂度为log(N)。但是在极端情况下,假如插入一组从大到小的数据,如图所示,则其查找的时间复杂度变为N。
红黑树是在二叉搜索树的基础上,加入红黑性质,使其成为一颗平衡二叉树,从而保证其查找的时间复杂度为log(N),被广泛应用于java的集合框架中,如TreeMap,TreeSet和ConcurrentHashMap。
二、红黑性质
一颗红黑树是满足下面红黑性质的二叉搜索树:
1)每个节点非黑即红。
2)根节点是黑色的。
3)空的叶子节点默认为黑色。
4)父节点是红色,其子节点必须为黑色。
5)从任意节点到其叶节点的每条路径上,都含有相同个数的黑色节点。
三、 基本操作
3.1. 左旋
3.2. 右旋
四、 插入
将一个新的节点按照下面顺序插入红黑树中:
1)将新节点按照二叉搜索树的插入方式插入
2)将新节点着为红色
如果插入的是节点默认为是黑色,不一定会引发冲突;反过来插入节点默认是红色也不一定只会违反“红色节点的子节点必须是黑色的”这一个条件;只是相对来说默认为红色冲突的可能较少。
3)将整棵树进行修正,使其符合红黑性质
因为插入的节点是红色节点,所以修正过程主要关注“红色节点的子节点必须是黑色的”这一特性即可,具体修正过程见4.1-4.4。
4.1. 当插入节点为根节点
直接将其着为黑色即可,如图所示。
4.2. 当插入节点的父节点为黑色
此时无需处理, 如图所示。
4.3. 当前节点的父节点为红色,其为祖父节点的左子节点
这种场景比较复杂,但其核心是将红色的节点移到根节点,然后将根节点设为黑色。可以细分为以下3种
4.3.1.1. 叔节点是黑色,当前节点是其父节点的左子节点
第1步将父节点设为黑色,将祖父节点设为红色;第2步以祖父节点为支点进行右旋,如图所示。
4.3.1.2. 叔节点是黑色,当前节点是其父节点的右子节点
第1步将当前节点5的父节点4进行左旋,然后4设为当前节点;
执行完后,当前节点4的父节点为红色且其位于父节点的左边,符合4.3.1.1的规则,照套即可。
4.3.1.3. 叔节点是红色
第1步将父节点和叔节点设为黑色,父节点设为红色,把祖父节点置为当前节点;
执行完后可以节点5的父节点为黑色,满足4.2的规则,照套进行。
4.4. 当前节点的父节点为红色,其为祖父节点的右子节点
规则同4,3类似,这里不再详叙,具体可以看代码。
五、 源码分析
5.1. 红黑树的节点
/**
* @author JayLai
* @Time 2018.01.22 22:36
* @param <T>
*/
public class RBTNode <T extends Comparable<T>>{
boolean color; //颜色
T key; //关键字(键值)
RBTNode<T> left; //左孩子
RBTNode<T> right; //右孩子
RBTNode<T> parent; // 父结点
/**
* 有参构造函数
* @param color
* @param key
* @param left
* @param right
* @param parent
*/
public RBTNode(boolean color, T key, RBTNode<T> left, RBTNode<T> right, RBTNode<T> parent) {
super();
this.color = color;
this.key = key;
this.left = left;
this.right = right;
this.parent = parent;
}
}
5.2. 红黑树
/**
* 红黑树
* 关建点:将红色的节点移到根节点,然后将根节点设为黑色
* @author JayLai
* @Time 2018.01.23 15:41
* @param <T>
*/
public class RBTree <T extends Comparable<T>>{
private RBTNode<T> root; //根节点
private static final boolean RED = false;
private static final boolean BLACK = true;
/**
* 无参构造函数
*/
public RBTree(){
root = null;
}
/**
* 返回指定节点的父节点
* @param node
* @return
*/
private RBTNode<T> parentOf(RBTNode<T> node){
return node != null ? node.parent : null;
}
/**
* 返回指定节点的颜色
* @param node
* @return
*/
private boolean colorOf(RBTNode<T> node){
return node != null ? node.color : BLACK;
}
/**
* 判断指定节点是否为红色
* @param node
* @return
*/
private boolean isRed(RBTNode<T> node){
return (node != null) && (node.color==RED) ? true : false;
}
/**
* 判断指定节点是否为黑色
* @param node
* @return
*/
private boolean isBlack(RBTNode<T> node){
return !isRed(node);
}
/**
* 将节点设为黑色
* @param node
*/
private void setBlack(RBTNode<T> node){
if(node != null){
node.color = BLACK;
}
}
/**
* 将节点设为红色
* @param node
*/
private void setRed(RBTNode<T> node){
if(node != null){
node.color = RED;
}
}
/**
* 设置指定节点的父节点
* @param node
* @param parent
*/
private void setParent(RBTNode<T> node, RBTNode<T> parent){
if(node != null){
node.parent = parent;
}
}
/**
* 设置指定节点的颜色
* @param node
* @param color
*/
private void setColor(RBTNode<T> node, boolean color){
if(node != null){
node.color = color;
}
}
/**
* 中序遍历
* @param node
*/
public void inOrder(RBTNode<T> node) {
if (node != null) {
// 中序遍历左子树
inOrder(node.left);
// 访问根节点
System.out.print("键值:" +node.key + " 颜色:" + ((isRed(node) == true ? "Red" : "Black")) + " ");
// 中序遍历右子树
inOrder(node.right);
}
}
/**
* 从小到达打印整一棵树红黑树
*/
public void printTree(){
if(root != null){
inOrder(root);
}
System.out.println();
}
/**
* 对红黑树的节点x进行左旋转
* @param x
*/
private void leftRotate (RBTNode<T> x){
RBTNode<T> y = x.right; //x的右子节点
x.right = y.left; //将y的左子节点设为x的右子节点
if(y.left != null){
y.left.parent = x; //将y的左子节点的父亲设为x
}
y.parent = x.parent; // 将x的父节点设为y的父节点
if(x.parent == null){ //如果x的父节点为空
this.root = y;
}else{
if(x.parent.left == x){ //如果x位于其父节点的左边
x.parent.left = y;
}else{ //如果x位于其父节点的右边
x.parent.right = y;
}
}
y.left = x; //将x设为y的左子节点
x.parent = y; //将x的父节点设为y
}
/**
* 对红黑树的节点x进行右旋转
*
* @param x
*/
public void rightRotate(RBTNode<T> x){
RBTNode<T> y = x.left; //x的左子节点
x.left = y.right; //将y的右子节点设为x的左子节点
if(y.right != null){
y.right.parent = x; // 将y的右子节点的父亲设为x
}
y.parent = x.parent; // 将x的父节点设为y的父节点
if(x.parent == null){ //如果x的父节点为空
this.root = y;
}else{
if(x.parent.left == x){ //如果x位于其父节点的左边
x.parent.left = y;
}else{ //如果x位于其父节点的右边
x.parent.right = y;
}
}
y.right = x; //将x设为y的右子节点
x.parent = y; //将x的父节点设为y
}
/**
* 通过添加键值(key)进行节点插入
* @param key
*/
public void insert(T key){
// 将新节点设为红色
RBTNode<T> newNode = new RBTNode<T>(RED, key, null, null, null);
if(newNode != null){
insert(newNode);
}
}
/**
* 将节点插到红黑树中
* @param node
*/
public void insert(RBTNode<T> node){
RBTNode<T> parent = null;
RBTNode<T> current = this.root;
int ret;
//1. 将节点插入到二叉树中
while(current != null){
parent = current;
ret = node.key.compareTo(current.key);
if(ret > 0){
current = current.right;
}else{
current = current.left;
}
}
node.parent = parent;
if(parent == null){
this.root = node;
}else{
ret = node.key.compareTo(parent.key);
if(ret < 0){
parent.left = node;
}else{
parent.right = node;
}
}
//2. 将其修正为红黑树
insertFixUp(node);
}
/**
* 将插入节点的二叉树修正为红黑树
* @param node
*/
private void insertFixUp(RBTNode<T> node){
RBTNode<T> parent, gparent;
// 当前节点的父节点是红色
while(((parent = parentOf(node))!=null) && isRed(parent)){
gparent = parentOf(parent);
//1父节点是祖父节点的左子节点
if(parent == gparent.left){
//case 1.1条件:叔叔节点是红色
//处理方法: 将父节设为黑色,将叔节点设为黑色,将祖父节点设为红色,将祖父节点设为当前节点
RBTNode<T> uncle = gparent.right;
if((uncle != null) && isRed(uncle)){
setBlack(uncle);
setBlack(parent);
setRed(gparent);
node = gparent;
continue;
}
//case 1.2条件:叔叔节点是黑色,当前节点是其父节点的右子节点
//叔节点若未为空也是黑色
//处理方法:将父点作为新的当前节点,以新的当前节点为支点进行左旋
if(parent.right == node){
RBTNode<T> tmp;
leftRotate(parent);
tmp = parent;
parent = node;
node = tmp;
}
//case 1.3条件:叔叔是黑色,且当前节点是左孩子
//处理方法: 将父节点点设为黑色,将祖父节点设为红色。以祖父节点为支点进行右旋。
setBlack(parent);
setRed(gparent);
rightRotate(gparent);
//2 父节点是祖父节点的左子节点
} else{
// Case 2.1条件:叔叔节点是红色
RBTNode<T> uncle = gparent.left;
if ((uncle!=null) && isRed(uncle)) {
setBlack(uncle);
setBlack(parent);
setRed(gparent);
node = gparent;
continue;
}
// Case 2.2条件:叔叔是黑色,且当前节点是左子节点
if (parent.left == node) {
RBTNode<T> tmp;
rightRotate(parent);
tmp = parent;
parent = node;
node = tmp;
}
// Case 2.3条件:叔叔是黑色,且当前节点是右子节点
setBlack(parent);
setRed(gparent);
leftRotate(gparent);
}
}
//将根节点设为黑色
setBlack(this.root);
}
}
5.3.测试代码
public class RBTreeApp {
public static void main(String[] args) {
int arr1[] = { 10 };
int arr2[] = { 10, 8};
int arr3[] = { 10, 8, 6};
int arr4[] = { 10, 8, 6, 4};
int arr5[] = { 10, 8, 6, 4, 5 };
RBTree<Integer> tree1 = new RBTree<Integer>();
RBTree<Integer> tree2 = new RBTree<Integer>();
RBTree<Integer> tree3 = new RBTree<Integer>();
RBTree<Integer> tree4 = new RBTree<Integer>();
RBTree<Integer> tree5 = new RBTree<Integer>();
for (int i = 0; i < arr1.length; i++) {
tree1.insert(arr1[i]);
}
for (int i = 0; i < arr2.length; i++) {
tree2.insert(arr2[i]);
}
for (int i = 0; i < arr3.length; i++) {
tree3.insert(arr3[i]);
}
for (int i = 0; i < arr4.length; i++) {
tree4.insert(arr4[i]);
}
for (int i = 0; i < arr5.length; i++) {
tree5.insert(arr5[i]);
}
tree1.printTree();
tree2.printTree();
tree3.printTree();
tree4.printTree();
tree5.printTree();
}
}
5.4. 测试结果