前提了解
红黑树
红黑树是自平衡的二叉搜索树,具有以下特性
- 结点是红色或黑色
- 根结点是黑色
- 所有叶子都是黑色(叶子是NIL结点)
- 每个红色结点的两个子结点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色结点)
- 从任一节结点到其每个叶子的所有路径都包含相同数目的黑色结点
红黑树的优势
红黑树吸收了2-3-4树和AVL树的优点,放弃了完美平衡的特性,改为局部平衡和完美黑色平衡
放弃2-3-4树的多节点,改为使用颜色来区分不同的节点类型,这样就降低了维护的成本和时间复杂度
红黑树链接方式讨论
- 以下对应2-3-4树为2-节点、左倾3-节点、右倾3-节点、4-节点
- 以下对应2-3-4树为插入4-节点分裂的过程,分裂到根节点黑色加一
- 一颗平衡的红黑树只会出现上述7种链接方式
插入情况讨论
- 每次插入的节点都是红节点
case0:第一次插入
- 操作:将根置黑
- 简单理解:根结点必须是黑色
- 对应2-3-4树:相当于第一次生成2-节点
case1:红节点插入到黑节点
- 操作:无需调整
- 对应2-3-4树:相当于把2-节点变成3-节点或把3-节点变成4-节点
- 故红节点插入到红节点才需要调整
case2:插入结点的父、叔节点为红节点
- 操作:父、叔置黑,祖置红,向上递归,若递归到根节点,则将根置黑,黑色加1
- 简单理解:插入两边为红
- 对应2-3-4树:插入4-节点并分裂,将中间值传给父节点,若递归到根,树高加1
case3:插入红节点左边,并且父在祖父左边
- 操作:父置黑,祖置红,对祖右旋
- 简单理解:产生左左连续红节点
- 对应2-3-4树:插入左倾3-节点的左边,生成4-节点
case4:插入红节点右边,并且父在祖父左边
- 操作:对父左旋,变成case3
- 简单理解:产生左右连续红节点,将其变成左左连续红节点
- 对应2-3-4树:插入左倾3-节点的中间,生成4-节点
case5:插入红节点右边,并且父在祖父右边
- 操作:父置黑,祖置红,对祖左旋
- 简单理解:产生右右连续红节点
- 对应2-3-4树:插入右倾3-节点的右边,生成4-节点
case6:插入红节点左边,并且父在祖父右边
- 操作:对父右旋,变成case5
- 简单理解:产生右左连续红节点,变成右右连续红节点
- 对应2-3-4树:插入右倾3-节点的中间,生成4-节点
删除情况判断
- 删除首先要找到节点,判断其是叶节点还是非叶节点
- 对应2-3-4树,将删除内部节点转为删除叶节点
case1:若待删除结点为叶节点,若为黑则先调整再删除,为红直接删除
case2:若待删除结点有一个子结点,用子结点替换待删除结点,再调整替换的子节点
这种情况只会出现在左倾3-节点或右倾3-节点
实际上case2是可以转为case1的,如下图
但TreeMap中代码将case1和case2分开进行了讨论,为什么要这样?
- 如果case2转为case1,会多了一个旋转操作和fixAfterDeletion()中的兄左右为黑的循环操作(即下面删除情况讨论中的case3)
- 调整的本质是调整待删除节点所在的不平衡子树,即调整其父和兄弟节点
case3:若待删除结点有两个子结点,用后继结点替换待删除结点,转为case1或case2
删除情况讨论
- 每次删除的都是叶节点,删除黑叶节点才需要调整,下面只讨论删除左边黑叶节点的情况,删除右边黑叶节点对称理解
- 对应2-3-4树:删除2-节点才需要调整
case1:兄弟节点为黑,并且有红右节点
操作:兄置父颜色,父置黑,兄右置黑,对父左旋
对应2-3-4树:兄弟节点是3或4-节点
其可能有4种情况,这里列出,下面为删除20,父为黑的情况
下面为删除70,父为红的情况
case2:兄弟节点为黑,并且无右节点、有红左节点
操作:兄左置黑,兄置红,对兄右旋,兄左成新兄,转为case1
对应2-3-4树:兄弟节点是3-节点(先将左倾3-节点转为右倾3-节点)
下面是删除20父为黑,删除70父为红两种情况,转为case1处理,剩下的处理步骤看上面
case3:兄弟节点为黑,左右节点不存在
操作:兄置红,父置黑(这种情况的红黑树只能通过删除构造)
对应2-3-4树:兄弟节点是2-节点
下面是删除20父为黑的情况
下面是删除70父为红的情况
case4:兄弟节点为红,转为黑再判断上述3种情况
操作:兄置黑,父置红,对父左旋,兄左成新兄,将兄转为黑
如下转为case1
如下转为case2
如下转为case3
至此,红黑树的左黑叶节点删除就介绍完了,右边对称同理就不再赘述
红黑树完整代码
以下代码是从TreeMap中抽取出的红黑树代码,上面的插入和删除情况讨论也是由TreeMap代码推导而来
class RbTree<K, V> {
private static final boolean RED = false;
private static final boolean BLACK = true;
private transient TreeMapEntry<K, V> root;
static final class TreeMapEntry<K, V> {
K key;
V value;
TreeMapEntry<K, V> left;
TreeMapEntry<K, V> right;
TreeMapEntry<K, V> parent;
boolean color = BLACK;
TreeMapEntry(K key, V value, TreeMapEntry<K, V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
}
public V put(K key, V value) { //插入,从根节点开始对比找到待插入的位置
TreeMapEntry<K, V> t = root;
if (t == null) {
root = new TreeMapEntry<>(key, value, null);
return null;
}
int cmp;
TreeMapEntry<K, V> parent;
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
TreeMapEntry<K, V> e = new TreeMapEntry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
return null;
}
private static <K, V> boolean colorOf(TreeMapEntry<K, V> p) {
return (p == null ? BLACK : p.color);
}
private static <K, V> TreeMapEntry<K, V> parentOf(TreeMapEntry<K, V> p) {
return (p == null ? null : p.parent);
}
private static <K, V> void setColor(TreeMapEntry<K, V> p, boolean c) {
if (p != null) p.color = c;
}
private static <K, V> TreeMapEntry<K, V> leftOf(TreeMapEntry<K, V> p) {
return (p == null) ? null : p.left;
}
private static <K, V> TreeMapEntry<K, V> rightOf(TreeMapEntry<K, V> p) {
return (p == null) ? null : p.right;
}
private void rotateLeft(TreeMapEntry<K, V> p) {
if (p != null) {
TreeMapEntry<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(TreeMapEntry<K, V> p) {
if (p != null) {
TreeMapEntry<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;
}
}
private void fixAfterInsertion(TreeMapEntry<K, V> x) { //插入后调整
x.color = RED; //每次插入的都为红节点
while (x != null && x != root && x.parent.color == RED) { //若不为根且父为红
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { //父在祖左边
TreeMapEntry<K, V> y = rightOf(parentOf(parentOf(x))); //y为叔
if (colorOf(y) == RED) { //叔为红,插入4-节点
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else { //叔为黑,插入3-节点
if (x == rightOf(parentOf(x))) { //插入右边,右左连续红节点
x = parentOf(x);
rotateLeft(x); //对父左旋调整为左左连续红节点
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else { //父在祖右边
TreeMapEntry<K, V> y = leftOf(parentOf(parentOf(x))); //y为叔
if (colorOf(y) == RED) { //叔为红,插入4-节点
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else { //叔为黑,插入3-节点
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; //根始终为黑
}
public V remove(Object key) { //删除首先找到键值对
TreeMapEntry<K, V> p = getEntry(key);
if (p == null)
return null;
V oldValue = p.value;
deleteEntry(p);
return oldValue;
}
final TreeMapEntry<K, V> getEntry(Object key) { //通过比较键,获取键值对
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
TreeMapEntry<K, V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
static <K, V> TreeMapEntry<K, V> successor(TreeMapEntry<K, V> t) { //获取后继节点
if (t == null)
return null;
else if (t.right != null) { //是右子树的最小值
TreeMapEntry<K, V> p = t.right;
while (p.left != null)
p = p.left;
return p;
} else { //若无右子树,则是第一个在左边的父节点
TreeMapEntry<K, V> p = t.parent;
TreeMapEntry<K, V> ch = t;
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p;
}
}
private void deleteEntry(TreeMapEntry<K, V> p) {
if (p.left != null && p.right != null) { //若存在左右节点,则用后继节点替代,问题转为删除后继节点
TreeMapEntry<K, V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
}
//若未进入上面if说明: 1.只有左节点 2.只有右节点 3.当前为叶节点
//若进入上面if说明: 1.后继节点为叶节点 2.后继节点只有右节点
//总结起来就是替换的节点可能:1.只有一个子节点 2.为叶节点
TreeMapEntry<K, V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) { //有一个子节点,子节点替换待删除节点
replacement.parent = p.parent;
if (p.parent == null)
root = replacement;
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
p.left = p.right = p.parent = null;
if (p.color == BLACK) //待删除节点为黑则需要平衡替换节点
fixAfterDeletion(replacement);
} else if (p.parent == null) { //没有子节点且没有父节点,说明当前删除根节点
root = null;
} else { //没有子节点,但有父节点,说明为叶节点
if (p.color == BLACK) //删除叶节点为黑则需要平衡
fixAfterDeletion(p);
if (p.parent != null) { //双向断开和父节点的链接
if (p == p.parent.left)
p.parent.left = null;
else if (p == p.parent.right)
p.parent.right = null;
p.parent = null;
}
}
}
private void fixAfterDeletion(TreeMapEntry<K, V> x) {
while (x != root && colorOf(x) == BLACK) { //待删除节点为黑且未递归到根
if (x == leftOf(parentOf(x))) { //删除左边的黑节点
TreeMapEntry<K, V> sib = rightOf(parentOf(x)); //sib为兄
if (colorOf(sib) == RED) { //这个if中兄为红,转为兄为黑的情况
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) { //这个if中兄右为黑(兄左为红)转为兄右为红
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 { //对称情况
TreeMapEntry<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); //x为根或3-节点的父节点
}
private void innerMidOrderTraversal(TreeMapEntry<K, V> node) {
if (node == null) {
return;
}
innerMidOrderTraversal(node.left);
System.out.print("[" + node.key + "-" + node.value + "]");
if (node.color == BLACK) {
System.out.print("b" + " ");
} else {
System.out.print("r" + " ");
}
innerMidOrderTraversal(node.right);
}
@NonNull
@Override
public String toString() {
System.out.print("中序遍历: ");
innerMidOrderTraversal(root);
return "";
}
}
代码是标准库拿的,可以完美运行,各种情况已在上面讨论,下面只进行一下简单测试
RbTree tree = new RbTree();
tree.put(50, "a");
System.out.println(tree);
tree.put(20, "b");
System.out.println(tree);
tree.put(80, "c");
System.out.println(tree);
tree.put(70, "d");
System.out.println(tree);
tree.remove(20);
System.out.println(tree);
打印如下
中序遍历: [50-a]b
中序遍历: [20-b]r [50-a]b
中序遍历: [20-b]r [50-a]b [80-c]r
中序遍历: [20-b]b [50-a]b [70-d]r [80-c]b
中序遍历: [50-a]b [70-d]b [80-c]b