红黑树是一棵自平衡的二叉搜索树。由于二叉树在插入或删除的时候会发生平衡失调,所以使二叉树平衡极为重要,而红黑树结构便是一个极为稳定的结构。
红黑树的每个节点都拥有颜色。红黑树需要满足以下五个性质:
1 每个节点或者是黑色,或者是红色。
2 根节点只能是黑色。
3 每个叶子节点必须为黑色。(这里的叶子节点为null的节点,和我以前理解的有所不同)
4 每个红色节点的子节点必须为黑色
5 从任意一个节点到其所有子节点的所有路径上的黑色节点数目必须相等。
由于在插入或删除节点时,红黑树将不会满足以上五个性质,所以需要进行旋转来满足这五个性质。
下面为对X节点进行左旋:
下面为对Y节点进行右旋:
所谓X节点左旋,就是讲X节点变为左儿子,而父节点便是X节点的右儿子。而Y节点右旋,就是将Y节点变为右儿子,而此时的父节点便是Y节点的左儿子。
以下为java TreeMap实现的左旋代码:
private void rotateLeft(Entry<K,V> p) {
if (p != null) {
Entry<K,V> r = p.right;
p.right = r.left;
if (r.left != null)
r.left.parent = p;
r.parent = p.parent;
if (p.parent == null)
root = r;
else if (p.parent.left == p)
p.parent.left = r;
else
p.parent.right = r;
r.left = p;
p.parent = r;
}
}
而下面为右旋代码:
private void rotateRight(Entry<K,V> p) {
if (p != null) {
Entry<K,V> l = p.left;
p.left = l.right;
if (l.right != null) l.right.parent = p;
l.parent = p.parent;
if (p.parent == null)
root = l;
else if (p.parent.right == p)
p.parent.right = l;
else p.parent.left = l;
l.right = p;
p.parent = l;
}
}
下面谈论关于插入后进行调整的见解:
插入节点前,需要将插入节点变为红色节点,为什么不插入黑色节点呢?我认为插入黑色节点后,性质5肯定不满足,且调整过于麻烦。
插入红色子节点X后,有三种情形:
1 插入的位置为root,将节点变为黑色
2 插入节点X的父节点为黑色,此时满足性质,不需要改变
3 插入节点X的父节点P为红色,此时需要调整红黑树。
此时又有三种情况:
1 X的叔节点为红色:
处理过程如图:
将父节点P和叔节点Y变成黑色,则祖父节点G必须设为红色,为了满足性质5,此时G节点变成了新的节点X,继续重复以上过程。
2 X的叔节点为黑色,但X节点是父节点的右儿子:
处理过程如图:对P点进行左旋,变成第三种情况。
3 X的叔节点为黑色,但X节点是父节点的左儿子:
处理过程如图:对G节点进行右旋,此时P节点成为父节点,由于右子树比左子树黑高多1,所以将G节点变为黑色,P节点也变为黑色,此时各个性质均满足。
Java实现的代码如下:
/** From CLR */
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
while (x != null && x != root && x.parent.color == RED) {//直接进入第三种情况
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {//判断X的父节点为X的祖父节点的左儿子
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) { //第一种情况,叔节点为红色
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == rightOf(parentOf(x))) { //第二种情况,叔节点为黑色,对X的父节点进行左旋
x = parentOf(x);
rotateLeft(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED); //第三种情况,叔节点为黑色,对X的祖父节点进行右旋
rotateRight(parentOf(parentOf(x)));
}
} else {
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
删除节点后的调整
先说说二叉树的删除吧。二叉树节点的删除分为三种情况:
1 删除的节点X无左右儿子,直接删除
2 删除的节点X有一个儿子,删除X,让X节点的儿子代替X节点
3 删除的节点x有两个儿子,那么找出X节点的后继节点去替代X节点的内容,而删除X的后继节点,此时变成1 或 2情况。
删除的节点Y有两种:红色节点或者黑色节点。红色节点删除后并不影响红黑树的性质,而删除黑色节点将会影响红黑树的性质4,5。
节点Y删除后,节点X代替节点Y的位置,由于Y为黑色节点,所以此时少了一个黑色节点。为了满足性质5,给节点X再赋予一个黑色。此时性质1将不再满足,现在应该把额外的黑色节点向上移动或者去掉来满足所有的性质。
X节点满足以下三种情况:
1 红+黑节点。处理方法:将该节点直接设置为黑色。
2 根节点且黑+黑节点。处理过程:将该节点设置为黑色。
3 非根节点且黑+黑节点。这种情况分为四种情形:
情形a:X节点的兄弟节点Y为红色,此时父节点必为黑色,转换如下图:
处理过程:将父节点P变成红色,兄弟节点Y变成黑色,然后P节点左旋转。此时变成情形b。
情形b:X节点的兄弟节点Y为黑色,Y节点的左右儿子节点均为黑色。如下图:
处理流程:将兄弟节点Y变为红色,节点P成为新的节点X。
情形c:X节点的兄弟节点Y为黑色,Y节点的右儿子节点YR为黑色,左儿子YL为红色,如下图:。
处理过程:兄弟节点Y变为红色,YL节点变为黑色,并且Y节点左旋。此时变为情形d。
情形d: X节点的兄弟节点Y为黑色,Y节点的右儿子节点YR为红色,左儿子YL任意色,如下图:
处理过程:兄弟节点Y变为父节点P的颜色,父节点P变为黑色,YR节点变为黑色,然后P节点左旋。最后将X节点设为root节点,完成。
Java代码实现如下:
private void fixAfterDeletion(Entry<K,V> x) {
while (x != root && colorOf(x) == BLACK) {//x不是根节点,并且是黑+黑节点
if (x == leftOf(parentOf(x))) { //X处于左子树
Entry<K,V> sib = rightOf(parentOf(x)); //兄弟节点处在右子树
if (colorOf(sib) == RED) { //兄弟节点为红色 的处理过程
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateLeft(parentOf(x));
sib = rightOf(parentOf(x));
}
if (colorOf(leftOf(sib)) == BLACK && //兄弟节点为黑色,并且左右儿子均为黑色
colorOf(rightOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(rightOf(sib)) == BLACK) { //兄弟节点为黑色,右儿子为黑色,左儿子为红色
setColor(leftOf(sib), BLACK);
setColor(sib, RED);
rotateRight(sib);
sib = rightOf(parentOf(x));
}
setColor(sib, colorOf(parentOf(x)));//兄弟节点为黑色,右儿子为红色,左儿子为任意色
setColor(parentOf(x), BLACK);
setColor(rightOf(sib), BLACK);
rotateLeft(parentOf(x));
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);
}