从五条性质谈起
说起红黑树的性质,一般引用《算法导论》中对红黑树的五条性质定义
- 每个节点或者是黑色,或者是红色。
- 根节点是黑色。
- 每个叶子节点(NIL)是黑色。
- 如果一个节点是红色的,则它的子节点必须是黑色的。
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
初次接触这五条性质时,一头雾水,虽然背下来不难,但是不知道为什么要这样定义。
换个角度,从二三树出发,可以真正理解这五个性质。
二三树
二三树是一种绝对平衡树,即所有的叶子节点都在同一层。
二三树的节点可以放置一或二个元素,同时具有二分搜索的特性,当节点有一个元素时,节点可以有左右两个子节点,称为二节点,左子节点<当前节点<右子节点。当节点有两个元素时,则有左中右三个子节点,称为三节点,左子节点<当前节点左元素<中子节点<当前节点右元素<右子节点。因为由二节点和三节点所组成,所以称为二三树。
我们可以这样表示二三树的节点,一个二节点由一个黑色节点表示,一个三节点由一个红色节点和一个黑色节点左右并列表示,我们始终将红色节点放在左边。这时候就可以发现,二三树可以等价转换为红黑树。
通过二三树重新理解红黑树的性质
此时我们重新理解那五条性质
- 每个节点或者是黑色,或者是红色。
因为三节点使用红色黑色两个节点表示,所以单个节点可能是红色也可能是黑色。 - 根节点是黑色。
因为我们定义三节点的表示红色节点在黑色节点左侧,可以理解为红色是黑色的左子节点。当根节点是二节点时,为黑色;当根节点是三节点时,根节点依然是黑色,只是左子节点为红色。所以根节点一定是黑色。 - 每个叶子节点(NIL)是黑色。
这条定义是对红黑树性质的补充完善。我们将空节点也认为是黑色,则即使根节点是空,也满足红黑树的性质。所以一棵空树也是红黑树。 - 如果一个节点是红色的,则它的子节点必须是黑色的。
因为红节点是三节点的左节点,而三节点与父节点相连的是右边的黑节点,所以红色节点无法连接到另一个红色节点 - 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
因为二三树是绝对平衡二叉树,所以每条路径上的节点数相同,而每个节点不论是二节点还是三节点,都有一个黑节点,所以每条路径上的黑节点数相等。
红黑树的java实现
红黑树的java实现可以参考TreeMap。
// 单个Node的结构
static final class Entry<K, V> implements Map.Entry<K, V> {
K key;
V value;
Entry<K, V> left;
Entry<K, V> right;
Entry<K, V> parent;
boolean color = BLACK;
/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry<K, V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
}
红黑树的添加
向红黑树中添加一个元素与二叉树类似,根据左大右小的规则,通过递归或循环找到放入元素的位置。关键在于放入后,使树继续满足红黑树的性质。这里看看TreeMap的做法。
private void fixAfterInsertion(Entry<K, V> x) {
// 首先将新节点x设为红色
x.color = RED;
// 如果x节点不为空且不为根节点且父节点是红色,走平衡逻辑
while (x != null && x != root && x.parent.color == RED) {
// 如果x的父节点是爷爷的左子
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
// y为x的右叔叔
Entry<K, V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
// 如果右叔叔y是红色
// 则p黑,y黑,g红(即颜色翻转)
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
// 将x设为爷爷节点,从爷爷节点开始重新走平衡逻辑
x = parentOf(parentOf(x));
} else {
// 如果右叔叔y是黑色,若y为普通节点,则左右路径黑色节点数不等,所以y为NIL
if (x == rightOf(parentOf(x))) {
// 如果x是右孩子,即
// g黑
// /
// p红
// \
// x红
x = parentOf(x);
// 对p进行右旋,形成
// g黑 g黑
// / 又因为x=parentOf(x) /
// x红 ,所以x还是指向最低层 p红
// / ------------------》 /
// p红 x红
rotateLeft(x);
}
// 将p染黑,g染红
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
// 对g进行右旋,形成
// p黑
// / \
// x红 g红
rotateRight(parentOf(parentOf(x)));
}
} else {
// 否则,x的父节点是爷爷的右子
// 则y为x的左叔叔
Entry<K, V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
// 如果y红
// 则p黑,y黑,g红(即颜色翻转)
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
// 将x设为爷爷节点,从爷爷节点开始重新走平衡逻辑
x = parentOf(parentOf(x));
} else {
// 否则y为NIL
if (x == leftOf(parentOf(x))) {
// 如果x是左子,即
// g黑
// \
// p红
// /
// x红
x = parentOf(x);
rotateRight(x);
// 形成
// g黑
// \
// p红
// \
// x红
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
// 形成
// p黑
// / \
// g红 x红
}
}
}
root.color = BLACK;
}
红黑树的删除
删除节点的部分与二叉树类似:若节点无子节点,直接删除。只有一个子节点,用子节点替换。有两个子节点,则找后继节点(比当前节点大的最小节点)替换。
关键在于删除节点后的维护红黑树性质。若删除的是红色节点则没有影响,若删除的是黑色节点,则 第五性质:每条路径上的黑节点数量相同 会被破坏(删除的是根节点除外),此时需要重新平衡。依然来看TreeMap的做法:
private void fixAfterDeletion(Entry<K, V> x) {
// 如果被删除的节点x不是根节点且是黑色
while (x != root && colorOf(x) == BLACK) {
if (x == leftOf(parentOf(x))) {
// 如果x是左子
// 找到x的右兄弟节点sib
Entry<K, V> sib = rightOf(parentOf(x));
if (colorOf(sib) == RED) {
// 如果sib是红色
// p
// / \
// x黑 sib红
// /\
// l黑r黑
// 将sib染黑,将x的父节点p染红
setColor(sib, BLACK);
setColor(parentOf(x), RED);
// p红
// / \
// x黑 sib黑
// /\
// l黑r黑
// 左旋p
rotateLeft(parentOf(x));
// sib黑
// /\
// p红 r黑
// /\
// x黑 l黑
// 重新找到旋转后的x的右兄弟sib
sib = rightOf(parentOf(x));
// 局部形成
// p红
// /\
// x黑 sib黑
}
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
// 如果sib的子节点都是黑色
// 则将sib染红
setColor(sib, RED);
// 将x指向x的父节点p,此时p为红,循环结束
x = parentOf(x);
// 形成
// p红
// /\
// x黑 sib红
// / \
// 黑 黑
// 方法最后会讲p染黑,形成平衡
} else {
// 如果sib的子节点不都是黑色
if (colorOf(rightOf(sib)) == BLACK) {
// p红
// /\
// x黑 sib黑
// / \
// 红 黑
// 如果sib的右子是黑色,将左子也染黑,将sib染红
setColor(leftOf(sib), BLACK);
setColor(sib, RED);
// p红
// /\
// x黑 sib红
// / \
// 黑l 黑r
// 右旋sib
rotateRight(sib);
// p红
// /\
// x黑 黑l
// \
// sib红
// \
// 黑r
// 重新找到旋转后的x的右兄弟sib
sib = rightOf(parentOf(x));
// 形成
// p红
// /\
// x黑 sib黑
// \
// 红
// \
// 黑
}
// 将sib的颜色设为p的颜色
setColor(sib, colorOf(parentOf(x)));
// 将p设为黑色
setColor(parentOf(x), BLACK);
// 将sib的右子设为黑色
setColor(rightOf(sib), BLACK);
// p黑
// /\
// x黑 sib红
// \
// 黑
// \
// 黑
// 左旋p
rotateLeft(parentOf(x));
// sib红
// / \
// p黑 黑
// / \
// x黑 黑
// 将x指向root(x为root则循环结束)
x = root;
}
} else { // symmetric
// 与上方的逻辑对称
Entry<K, V> sib = leftOf(parentOf(x));
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateRight(parentOf(x));
sib = leftOf(parentOf(x));
}
if (colorOf(rightOf(sib)) == BLACK &&
colorOf(leftOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(leftOf(sib)) == BLACK) {
setColor(rightOf(sib), BLACK);
setColor(sib, RED);
rotateLeft(sib);
sib = leftOf(parentOf(x));
}
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(leftOf(sib), BLACK);
rotateRight(parentOf(x));
x = root;
}
}
}
// 染黑
setColor(x, BLACK);
}