红黑树的简介
红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树。
红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值。
除了具备该特性之外,红黑树还包括许多额外的信息。
红黑树的每个节点上都有存储位表示节点的颜色,颜色是红(Red)或黑(Black)。
红黑树的特性:
(1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
红黑树的结构
红黑树的结构定义
public class RBTree<T extends Comparable<T>> {
// 根节点
private RBTNode<T> root;
// 节点颜色标识
private static final boolean RED = false;
private static final boolean BLACK = true;
// 红黑树节点
class RBTNode<T extends Comparable<T>> {
boolean color; // 节点颜色
T key; // 键值
RBTNode leftChild; // 左孩子
RBTNode rightChild; // 右孩子
RBTNode parent; // 父节点
public RBTNode(boolean color, T key, RBTNode leftChild, RBTNode rightChild, RBTNode parent) {
this.color = color;
this.key = key;
this.leftChild = leftChild;
this.rightChild = rightChild;
this.parent = parent;
}
}
...........
红黑树的左旋和右旋
/*
* 对红黑树的节点(x)进行左旋转
*
* 左旋示意图(对节点x进行左旋):
* px px
* / /
* x y
* / \ --(左旋)-. / \ #
* lx y x ry
* / \ / \
* ly ry lx ly
*/
private void leftRotate(RBTNode node) {
// 将 node 节点的右孩子赋值给 y
RBTNode y = node.rightChild;
// y 的左孩子存在
if (y.leftChild != null) {
// 把 y 的左孩子的父节点设置为 node,把 node 的右孩子设置为 y 的左孩子
y.leftChild.parent = node;
node.rightChild = y.leftChild;
}
// node 节点父节点为空,则为根节点,将 y 节点设置为根节点
if (node.parent == null) {
this.root = y;
} else if (node.parent.leftChild == node) { // 不为根节点时,node 节点是父节点的左/右孩子,就将 y 设置为父节点的左/右孩子
node.parent.leftChild = y;
} else {
node.parent.rightChild = y;
}
// y 的父节点设置为 node 的父节点
y.parent = node.parent;
// y 的左孩子设置为 node
y.leftChild = node;
// node 的父节点设置为 y
node.parent = y;
}
/*
* 对红黑树的节点(y)进行右旋转
*
* 右旋示意图(对节点y进行左旋):
* py py
* / /
* y x
* / \ --(右旋)-. / \ #
* x ry lx y
* / \ / \ #
* lx rx rx ry
*/
private void rightRotate(RBTNode node) {
RBTNode x = node.leftChild;
if (x.rightChild != null) {
x.rightChild.parent = node;
node.leftChild = x.rightChild;
}
if (node.parent == null) {
this.root = x;
} else if (node.parent.leftChild == node) {
node.parent.leftChild = x;
} else {
node.parent.rightChild = x;
}
x.rightChild = node;
x.parent = node.parent;
node.parent = x;
}
插入方法
/**
* 外部调用
* @param key 值
*/
public void insert(T key) {
RBTNode<T> node = new RBTNode<>(RED, key, null, null, null);
if (node != null)
insert(node);
}
/**
* 插入节点
* @param node
*/
private void insert(RBTNode<T> node) {
RBTNode<T> x = this.root;
RBTNode<T> y = null;
while (x != null) { // 终止条件
y = x; // 循环条件
x = (node.key.compareTo(x.key) < 0) ? x.leftChild : x.rightChild; // 比较大小,确定插入左/右子树
}
// 设置父节点
node.parent = y;
if (y != null) { // 判断放在左/右孩子
if (y.key.compareTo(node.key) < 0)
y.rightChild = node;
else y.leftChild = node;
} else this.root = node;
// 重新调整为红黑树
insertFixUp(node);
}
/*
* 调整红黑树
* 修复颜色,因为插入的点,我们总是将其先染成红色,所以如果父节点为黑色则不需要修复,如果父节点为红色,则需要修复
* 修复颜色分三种情况:
* ①当前插入点的父亲为红色,且祖父节点的另一个节点为也为红色,且父节点为祖父节点的左子节点
* ②当前插入点的父节点为红色,且祖父节点的另一个节点为黑色,且本节点为父结点的右子节点
* ③当前插入点的父节点为红色,且祖父节点的另一个节点为黑色,且本节点为父结点的左子节点
*/
private void insertFixUp(RBTNode<T> node) {
// 获取传入节点的父节点,祖父节点,叔父节点
RBTNode<T> parent,grandPa,uncle;
parent = node.parent;
grandPa = parent.parent;
uncle = (parent == grandPa.leftChild) ? grandPa.rightChild : grandPa.leftChild;
while (!parent.color) { // 父节点为红色进入循环
if (uncle != null && !uncle.color) { // 叔父节点不为空且叔父节点为红色,情况一
parent.color = BLACK; // 父节点变黑
uncle.color = BLACK; // 叔父节点变黑
grandPa.color = RED; // 祖父节点变红
insertFixUp(grandPa); // 从祖父节点处重新调整红黑树
} else { // 叔父节点为黑色或者叔父节点是空节点
if (node == parent.rightChild) { // 情况二,节点是右孩子
leftRotate(parent); // 对父节点进行左旋转
insertFixUp(parent); // 从父节点处重新调整红黑树
} else { // 情况三,节点是左孩子
parent.color = BLACK;
grandPa.color = RED;
rightRotate(grandPa); // 变色后对祖父节点进行右旋转,旋转后跳出循环
break;
}
}
}
this.root.color = BLACK; // 最后把根节点变黑
}
参考博客
链接: 【算法导论】红黑树详解之一(插入).
链接: 红黑树(五)之 Java的实现.