红黑树是一种近似平衡的二叉查找树,它能够确保任何一个节点的左右子树的高度差不会超过二者中较低那个的一倍。具体来说,红黑树是满足如下条件的二叉查找树(binary search tree):
- 每个节点要么是红色,要么是黑色。
- 根节点必须是黑色
- 红色节点不能连续(也即是,红色节点的孩子和父亲都不能是红色)。
- 对于每个节点,从该点至null(树尾端)的任何路径,都含有相同个数的黑色节点。
在树的结构发生改变时(插入或者删除操作),往往会破坏上述条件3或条件4,需要通过调整使得查找树重新满足红黑树的条件。
预备知识
前文说到当查找树的结构发生改变时,红黑树的条件可能被破坏,需要通过调整使得查找树重新满足红黑树的条件。调整可以分为两类:一类是颜色调整,即改变某个节点的颜色;另一类是结构调整,集改变检索树的结构关系。结构调整过程包含两个基本操作:左旋(Rotate Left),右旋(RotateRight)。
左旋
左旋的过程是将x的右子树绕x逆时针旋转,使得x的右子树成为x的父亲,同时修改相关节点的引用。旋转之后,二叉查找树的属性仍然满足。
右旋
右旋的过程是将x的左子树绕x顺时针旋转,使得x的左子树成为x的父亲,同时修改相关节点的引用。旋转之后,二叉查找树的属性仍然满足。
package com.kun.kunspringbootweb.foo.tree;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* 红黑树-Java实现例子
*/
public class MyRBTree<T extends Comparable<T>, D> {
private RBNode<T, D> root;//根节点
/**
* 节点的颜色
*/
private static final Boolean RED = false;
private static final Boolean BLACK = true;
public class RBNode<T extends Comparable<T>, D> {
private Boolean color;//节点颜色
private T key;//键值
private D data;//具体的数据
private RBNode<T, D> parent;
private RBNode leftChild;
private RBNode rightChild;
public RBNode(Boolean col, T key, D data, RBNode paret, RBNode leftChild, RBNode rightChild) {
this.color = col;
this.key = key;
this.data = data;
this.parent = parent;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
}
/**
* 获取父亲
*
* @param node
* @return
*/
public RBNode<T, D> parentOf(RBNode<T, D> node) {
if (node != null) {
return node.parent;
}
return null;
}
/**
* 获取颜色
*
* @param node
* @return
*/
public Boolean colorOf(RBNode<T, D> node) {
if (node != null) {
return node.color;
}
return BLACK;
}
public void setParent(RBNode<T, D> node, RBNode<T, D> parent) {
if (node != null) {
node.parent = parent;
}
}
public void setColor(RBNode<T, D> node, Boolean color) {
if (node != null) {
node.color = color;
}
}
public Boolean isRed(RBNode<T, D> node) {
return (node != null && node.color == RED) ? true : false;
}
public Boolean isBlack(RBNode<T, D> node) {
return !isRed(node);
}
public void setRed(RBNode<T, D> node) {
if (node != null) {
node.color = RED;
}
}
public void setBlack(RBNode<T, D> node) {
if (node != null) {
node.color = BLACK;
}
}
/**
* 根据key获取数据
*
* @param key
* @return
*/
public D get(T key){
RBNode node = search(key, root);
return node == null ? null : (D) node.data;
}
//寻找为key值的节点
public RBNode<T, D> search(T key, RBNode<T, D> node) {
if (node != null) {
//查找的过程,就是一直递归比较到叶子为止
int com = key.compareTo(node.key);
if (com < 0) {
return search(key, node.leftChild);
} else if (com > 0) {
return search(key, node.rightChild);
} else {
return node;
}
}
return null;
}
//寻找后继节点,即大于该节点的最小节点
public RBNode<T, D> min(RBNode<T, D> node) {
//一直往左走,最左端的就是最小值,这是二叉树的性质
if (node.leftChild == null) {
return node;
}
while (node.leftChild != null) {
node = node.leftChild;
}
return node;
}
/**
* 寻找待删节点的后继节点
* (因为这个节点即将要被删了,所以要选个后继节点补到这个位置来,
* 选择节点的规则:
* 这个规则和普通二叉树是一样的,要么就是找左子树的最大值,要么就是右子树的最小值)
*
* @param node
* @return
*/
public RBNode successor(RBNode<T, D> node) {
if (node.rightChild != null) {
return min(node.rightChild);
}
//下面这里是不会进入的,因为只有node的两个孩子都不为null时才会进入这个方法
RBNode<T, D> y = node.parent;
while ((y != null) && (y.rightChild == node)) {
node = y;
y = y.parent;
}
return y;
}
/**
* 对某个节点进行左旋
* (当前节点就是父亲节点,整体过程就是 父亲下沉,右孩子上升,然后右孩子的左节点变成了原父亲的右节点)
*
* @param x
*/
public void leftRonate(RBNode<T, D> x) {
//右孩子
RBNode<T, D> y = x.rightChild;
if (y.leftChild != null) {
//当前节点 变成了 右孩子的左节点的父亲
y.leftChild.parent = x;
}
x.rightChild = y.leftChild;
y.leftChild = x;
//当前的父亲变成了右孩子的父亲
y.parent = x.parent;
if (x.parent != null) {
if (x.parent.leftChild == x) {
x.parent.leftChild = y;
} else {
x.parent.rightChild = y;
}
} else {
this.root = y;