从TreeMap源码理解红黑树原理
红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
红黑树的性质
1)每个节点要么是黑色,要么是红色
2)根节点是黑色
3)每个叶子节点(空结点NIL)是黑色
4)每个红色结点的两个子结点一定都是黑色
5)任意一结点到每个叶子结点的路径都包含数量相同的黑结点(确保没有一条路径比其他路径长出2倍)
基本操作
1. 左旋
// TreeMap<K, V> 源码,对以p节点为根节点的子树进行左旋操作
private void rotateLeft(Entry<K,V> p) {
if (p != null) {
Entry<K,V> r = p.right; // 设r为当前节点p的右孩子
p.right = r.left; // 步骤1:r的左孩子设为当前结点的右孩子
if (r.left != null)
r.left.parent = p;
r.parent = p.parent; // 步骤2:将r设为当前子树的根节点
if (p.parent == null)
root = r;
else if (p.parent.left == p)
p.parent.left = r;
else
p.parent.right = r;
r.left = p; // 步骤3:将当前结点p设置为r的左孩子
p.parent = r;
}
}
2. 右旋
// TreeMap<K, V> 源码,对以p节点为根节点的子树进行右旋操作
private void rotateRight(Entry<K,V> p) {
if (p != null) {
Entry<K,V> l = p.left; // 设l为当前节点p的左孩子
p.left = l.right; // 步骤1:l的右孩子设为当前结点p的左孩子
if (l.right != null) l.right.parent = p;
l.parent = p.parent; // 步骤2:将l设为当前子树的根节点
if (p.parent == null)
root = l;
else if (p.parent.right == p)
p.parent.right = l;
else p.parent.left = l;
l.right = p; // 步骤3:将当前结点p设置为l的右孩子
p.parent = l;
}
}
3. 插入操作
// TreeMap<K, V>源码,插入操作
public V put(K key, V value) {
Entry<K,V> t = root; // t为红黑树的根节点
if (t == null) { // 当前红黑树为空的情况
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null); // 创建新的结点
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
// 将红黑树看作二叉排序树寻找插入的位置(插入到parent结点的子节点处)
if (cpr != null) {
// 判断是否有通过构造器传入比较器,即TreeMap(Comparator<? super K> comparator)
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else { // 没有通过构造器传入比较器的情况,则使用key对象实现的Comparable接口进行比较
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);
}
Entry<K,V> e = new Entry<>(key, value, parent); // 创建新节点
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e); // 对红黑树进行插入后调整操作,实树重新变为红黑树
size++;
modCount++;
return null;
}
// 插入后的调整操作,插入后的调整主要看当前节点的叔叔节点(祖父结点的另一个孩子)的颜色
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED; // 首先将插入节点设为红保证满足性质5
while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { // 1 父节点为祖父节点的左孩子
Entry<K,V> y = rightOf(parentOf(parentOf(x))); // y为当前结点的叔叔节点
if (colorOf(y) == RED) { // 1.1 叔叔节点为红色:变色(Case1)
setColor(parentOf(x), BLACK); // 将父结点设为黑色
setColor(y, BLACK); // 将叔叔节点设为黑色
setColor(parentOf(parentOf(x)), RED); // 将祖父节点设为红色
x = parentOf(parentOf(x)); // 设置当前结点为祖父节点
} else { // 1.2 叔叔节点为黑色:旋转
if (x == rightOf(parentOf(x))) { // 1.2.1如果当前节点为右孩子(Case2)
x = parentOf(x); // 设置父节点为当前节点x
rotateLeft(x); // 对当前节点左旋
} // 1.2.2 当前结点为左孩子(Case3)
setColor(parentOf(x), BLACK); // 设置父节点为黑色
setColor(parentOf(parentOf(x)), RED); // 设置祖父节点为红色
rotateRight(parentOf(parentOf(x))); // 对祖父节点进行右旋
}
} else { // 2 父节点为祖父节点的右孩子, 与上对称
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) { // 2.1
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else { // 2.2
if (x == leftOf(parentOf(x))) { // 2.2.1
x = parentOf(x);
rotateRight(x);
} // 2.2.2
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
针对插入后的调整操作的三种情形进行分析:(以“1 父节点为祖父节点的左孩子”为例)
Case 1 : “叔叔节点为红色”
解决方案:将父结点和叔叔节点变为黑色、祖父节点变为红色。最后将祖父节点设为当前节点。
Case 2:“叔叔结点为黑色,且当前节点为右孩子”
解决方案:将父节点设为当前节点,然后对当前节点,进行左旋进入Case3情形。
Case 3:“叔叔结点为黑色,且当前节点为左孩子”
解决方案:将父节点设为黑色,祖父节点设为红色,对祖父节点进行右旋操作,调整结束。
4. 删除操作
// TreeMap<K, V> 源码,删除操作
public V remove(Object key) {
Entry<K,V> p = getEntry(key); // 查找key在红黑树中的位置
if (p == null)
return null;
V oldValue = p.value;
deleteEntry(p); // 删除找到的节点
return oldValue;
}
private void deleteEntry(Entry<K,V> p) {
modCount++;
size--;
if (p.left != null && p.right != null) {
// p有两个孩子的情况,用它的后继节点来代替它进行删除
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
}
// **此时删除节点p最多只有一个孩子**,找到replacement节点放入删除p节点后的位置。
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) { // 此时删除节点有一个孩子节点replacement
// 用replacement节点替换p结点的位置
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
p.left = p.right = p.parent = null;
if (p.color == BLACK) // 如果删除的结点的颜色为黑色,则破坏了性质5,需要调整
fixAfterDeletion(replacement);
} // 此时删除节点无孩子结点
else if (p.parent == null) { // return if we are the only node.
root = null;
} else { // No children. Use self as phantom replacement and unlink.
if (p.color == BLACK)
fixAfterDeletion(p); // 先调整,再删除(和《算法导论》中伪代码不同,但功能一致)
// 删除节点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(Entry<K,V> x) {
while (x != root && colorOf(x) == BLACK) {
if (x == leftOf(parentOf(x))) { // 1 当前节点x为左孩子
Entry<K,V> sib = rightOf(parentOf(x)); // sib为x的兄弟节点
if (colorOf(sib) == RED) {
// 1.1 兄弟节点为红色 -> 转化为1.2情况(Case1)
setColor(sib, BLACK); // 兄弟节点设为黑色
setColor(parentOf(x), RED); // 父节点设为红色
rotateLeft(parentOf(x)); // 对父结点进行左旋操作(此次旋转不改变结点到每个叶子结点的路径上的黑结点数)
sib = rightOf(parentOf(x)); // 维护sib指向x的兄弟节点
}
// 1.2 兄弟节点为黑色
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
// 1.2.1 sib的左右孩子都为黑色 -> 重新开始调整(Case2)
setColor(sib, RED); // 将兄弟节点sib设为红色
x = parentOf(x); // 设置x的父节点为当前节点x
// **** 第1处循环终止位置 ****
} else {
if (colorOf(rightOf(sib)) == BLACK) {
// 1.2.2 sib的左孩子为红色,右孩子为黑色 -> 转化为1.2.3的情况(Case3)
setColor(leftOf(sib), BLACK); // 将sib的左孩子设为黑色
setColor(sib, RED); // 将sib设置为红色
rotateRight(sib); // 对sib进行左旋(此次旋转不改变结点到每个叶子结点的路径上的黑结点数)
sib = rightOf(parentOf(x)); // 维护sib指向x的兄弟节点
}
// 1.2.3 sib的右孩子为红色(Case4)
setColor(sib, colorOf(parentOf(x))); // 将sib的颜色设为x的父节点颜色
setColor(parentOf(x), BLACK); // x的父节点设为黑色
setColor(rightOf(sib), BLACK); // sib的右节点设为黑色
rotateLeft(parentOf(x)); // 对x的父节点进行右旋(此次旋转会使到父节点的左子树中的叶子结点的路径上的黑结点数+1)
x = root; // x设为根结点终止循环
// **** 第2处循环终止位置 ****
}
} else { // 2 当前节点x为右孩子,与上对称
Entry<K,V> sib = leftOf(parentOf(x));
if (colorOf(sib) == RED) {
// (Case1)
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateRight(parentOf(x));
sib = leftOf(parentOf(x));
}
if (colorOf(rightOf(sib)) == BLACK &&
colorOf(leftOf(sib)) == BLACK) {
// (Case2)
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(leftOf(sib)) == BLACK) {
// (Case3)
setColor(rightOf(sib), BLACK);
setColor(sib, RED);
rotateLeft(sib);
sib = leftOf(parentOf(x));
}
// (Case4)
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(leftOf(sib), BLACK);
rotateRight(parentOf(x));
x = root;
}
}
}
setColor(x, BLACK); // 最后将x设为黑色节点
}
删除后的调整是针对删除黑色节点做的,由于删除了一个黑色节点导致到删除位置路径上的黑色节点少一个,不满足性质5,调整的目标是使到该位置路径上的黑色节点增加1。
下面对上述的四种情况进行分析(以当前节点是左孩子为例):
Case1:“兄弟节点为红色”
解决方案:将父节点设为红色,兄弟节点设为黑色,对父节点进行左旋操作。
Case2:"兄弟节点为黑色,且兄弟节点的左右孩子都为黑色"
解决方案:将兄弟节点变为红色,并重新设置当前节点为父节点。
Case3:“兄弟节点为黑色,且兄弟节点的左孩子为红色,右孩子为黑色”
解决方案:将兄弟节点设为红色,兄弟节点的左孩子设为黑色,对兄弟节点进行右旋操作。进入Case4情形。
Case4:“兄弟节点为黑色,且兄弟节点的右孩子为红色”
解决方案:将兄弟节点设为父节点的颜色,将父节点和兄弟节点的右孩子设为黑色,对父节点进行左旋操作。