1. 前身2-3-4树
学习红黑树之间,我们需要了解其前身,也就是2-3-4树。该树结构是一种绝对平衡的多叉树,有3种节点类型:
- 2节点:这种节点有2个子节点
- 3节点:这种节点有3个子节点
- 4节点:这种节点有4个子节点
1.1 添加操作
以下是往一棵2-3-4树添加元素1-10的过程,该过程很好地体现出2-3-4树的绝对平衡性质。
1.2 与红黑树的比较
我们先了解一下红黑树的以下几个性质:
- 每个节点是红色或者黑色
- 根节点是黑色
- 红色节点的父节点或子节点都是黑色,即不存在连续两个红色节点
- 叶子节点都是不存放数据的空节点,称为
NIL节点(其他文章里可能有其他的称呼,但意思一样),该节点也是黑色 - 从任一节点出发,到任一NIL叶子节点都会经过相同数量的黑色节点,即黑平衡
如下图就是一棵红黑树,从根节点15出发,到任一NIL叶子节点都经过了相同数量的黑色节点
那么红黑树与上述提到的2-3-4树有什么关系呢?当我们把红色节点"移动到"与其父节点同一高度时,我们就能发现它就变成了一棵2-3-4树,如下图所示。红黑树的平衡性质也就能直观地体现出来。
1.3 总结
- 红黑树中黑色节点个数 等于 2-3-4树的节点个数
- 如果一个节点是红色,那么它对应的2-3-4树的节点位置,是在两边
- 否则如果一个节点是黑色,那么它对应的2-3-4树的节点位置,是在中间
2. 红黑树的操作
通过章节1的内容,我们已经掌握了红黑树的前身和性质,下面介绍红黑树的操作。
2.1 添加
红黑树的添加操作共有12种场景,如下图所示:8种场景是在红色节点下添加,4种场景是在黑色节点下添加。注意:新添加的节点默认为红色,因为红色节点并不影响红黑树的高度/黑平衡,以红色节点添加进来更能满足红黑树的性质,越满足红黑树的性质,后续的修复操作就越少。
2.1.1 CASE 9-12
这4种场景最简单,因为是在黑色节点下添加,相当于在2-3-4树中,往2节点或者3节点添加一个节点,这并不影响整棵树的高度,且不违背红黑树的性质,因此这4种场景没有修复操作。
剩下的场景都是在红色节点下进行添加,由于红黑树不能有连续两个红色节点,因此后续的8种场景都会有对应的修复操作。
2.1.2 CASE 1-4
这4种场景相当于在2-3-4树的4节点进行添加操作,修复操作操作如下:
- 把父节点、叔叔节点变为黑色,祖父节点变为红色
- 祖父节点变为红色后,把它看成一个新添加的节点,向上进行递归添加操作。对应在2-3-4树的
4节点进行添加时的上溢 - 最终,如果根节点无法"消化"递归传递上来的的红色节点,则整棵树的黑色高度会加一
2.1.3 CASE 6和7
这两种场景是对称的,添加位置分别是RR(父节点是祖父节点的右孩子,新节点是父节点的右孩子)和LL,以CASE 6为例进行介绍。对应在2-3-4树的3节点添加数据变成4节点,但由于违背不存在连续两个红色节点的性质,因此需要修复,修复操作如下:
- 父节点变为黑色、祖父节点变为红色
- 左旋祖父节点
操作完成后,即可结束,不需要向上递归操作。
2.1.4 CASE 5和8
这两种场景是对称的,添加位置分别是RL(父节点是祖父节点的右孩子,新节点是父节点的左孩子)和LR,以CASE 5为例进行介绍。对应在2-3-4树的3节点添加数据变成4节点,但由于违背不存在连续两个红色节点的性质,因此需要修复,修复操作如下:
- 右旋父节点
- 把原来的父节点看成是新添加的节点,此时变成与CASE 6一样的场景
- 重复CASE 6的修复操作
操作完成后,即可结束,不需要向上递归操作。
2.2 删除
挖坑待更新。。。
3. 代码实现
以下代码逻辑与JAVA的TreeMap实现一致。
public class RedBlackTree<K> {
private static final int RED = 1;
private static final int BLACK = 0;
private final Comparator<K> comparator;
private final Node<K> NIL;
private Node<K> root;
private static class Node<K> {
private K data;
private Node<K> left;
private Node<K> right;
private Node<K> parent;
private int color;
}
public RedBlackTree(Comparator<K> comparator) {
this.comparator = comparator;
NIL = new Node<>();
NIL.color = BLACK;
NIL.left = null;
NIL.right = null;
root = NIL;
}
private Node<K> minimum(Node<K> node) {
while (node.left != NIL) {
node = node.left;
}
return node;
}
private Node<K> maximum(Node<K> node) {
while (node.right != NIL) {
node = node.right;
}
return node;
}
/**
* 对X进行左旋
*
* p p
* | |
* X -----> Y
* / \ / \
* a Y X c
* / \ / \
* b c a b
*/
private void leftRotate(Node<K> x) {
Node<K> y = x.right;
x.right = y.left;
if (y.left != NIL) {
y.left.parent = x;
}
Node<K> p = x.parent;
y.parent = p;
if (p == null) {
this.root = y;
} else if (x == p.left) {
// 如果x是p的左子节点,那么y就应该变为左子节点
p.left = y;
} else {
p.right = y;
}
y.left = x;
x.parent = y;
}
/**
* 对X进行右旋
*
* p p
* | |
* X -----> Y
* / \ / \
* Y c a X
* / \ / \
* a b b c
*/
private void rightRotate(Node<K> x) {
Node<K> y = x.left;
x.left = y.right;
if (y.right != NIL) {
y.right.parent = x;
}
Node<K> p = x.parent;
y.parent = p;
if (p == null) {
this.root = y;
} else if (x == p.right) {
p.right = y;
} else {
p.left = y;
}
y.right = x;
x.parent = y;
}
/**
* 插入一个新元素
*/
public void insert(K data) {
// 创建一个新节点
Node<K> node = new Node<>();
node.parent = null;
node.data = data;
node.left = NIL;
node.right = NIL;
// 新节点默认红色
node.color = RED;
Node<K> p = null;
Node<K> x = this.root;
while (x != NIL) {
p = x;
if (comparator.compare(data, x.data) < 0) {
x = x.left;
} else {
x = x.right;
}
}
// 此时x是最下层的NIL空节点,p是新节点的父节点
node.parent = p;
if (p == null) {
this.root = node;
} else if (comparator.compare(data, p.data) < 0) {
p.left = node;
} else {
p.right = node;
}
// 如果新节点就是根节点,变为黑色,直接返回
if (node.parent == null) {
node.color = BLACK;
return;
}
// 如果新节点是根节点的左/右子节点,直接返回
if (node.parent.parent == null) {
return;
}
fixInsert(node);
}
/**
* 插入新节点后的修复
* @param k 插入后的新节点
*/
private void fixInsert(Node<K> k) {
// 叔叔节点,即k的父节点的兄弟节点
Node<K> uncle;
// 当父节点是红色时,才需要修复,即前8个CASE
while (k.parent.color == RED) {
if (k.parent == k.parent.parent.right) {
// 如果父节点是右节点,对应CASE 3、4、5、6
uncle = k.parent.parent.left;
if (uncle.color == RED) {
// 如果叔叔节点是红色,对应CASE 3、4
// 此时把叔叔节点、父节点变为黑色、祖父节点变为红色
uncle.color = BLACK;
k.parent.color = BLACK;
k.parent.parent.color = RED;
// 把祖父节点赋给k(即把祖父节点看成新节点),向上循环调整
k = k.parent.parent;
} else {
// 否则叔叔为黑色时,对应CASE 5、6 (此时叔叔节点是黑色的NIL)
if (k == k.parent.left) {
// 如果是CASE 5,对父节点进行右旋,旋转后的箭头方向与CASE 6相同
k = k.parent;
rightRotate(k);
}
// 父节点变为黑色、祖父节点变为红色,然后左旋祖父节点,修复完成
k.parent.color = BLACK;
k.parent.parent.color = RED;
leftRotate(k.parent.parent);
}
} else {
// 否则父节点是左节点,对应CASE 1、2、7、8
uncle = k.parent.parent.right;
if (uncle.color == RED) {
// 如果叔叔节点是红色,对应CASE 1、2
// 此时把叔叔节点、父节点变为黑色、祖父节点变为红色
uncle.color = BLACK;
k.parent.color = BLACK;
k.parent.parent.color = RED;
// 把祖父节点赋给k(即把祖父节点看成新节点),向上循环调整
k = k.parent.parent;
} else {
// 否则叔叔为黑色时,对应CASE 7、8 (此时叔叔节点是黑色的NIL)
if (k == k.parent.right) {
// 如果是CASE 8,对父节点进行左旋,旋转后的箭头方向与CASE 7相同
k = k.parent;
leftRotate(k);
}
// 父节点变为黑色、祖父节点变为红色,然后右旋祖父节点,修复完成
k.parent.color = BLACK;
k.parent.parent.color = RED;
rightRotate(k.parent.parent);
}
}
if (k == root) {
break;
}
}
root.color = BLACK;
}
}