今天总结一下红黑树的删除以及删除之后的调整过程,还是基于1.8 TreeMap的源码。
删除
红黑树的删除操作:
private void deleteEntry(Entry<K,V> p) {
modCount++;
size--;
// If strictly internal, copy successor's element to p and then make p
// point to successor.
if (p.left != null && p.right != null) {
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
} // p has 2 children
// Start fixup at replacement node, if it exists.
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
// Link replacement to parent
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;
// Null out links so they are OK to use by fixAfterDeletion.
p.left = p.right = p.parent = null;
// Fix replacement
if (p.color == BLACK)//如果删除的节点为黑色,进行调整
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);
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))) {//x为其父节点的左子节点
Entry<K,V> sib = rightOf(parentOf(x));//取x的兄弟节点,即父节点的右子节点,记为sib
if (colorOf(sib) == RED) {//情况1:兄弟节点为红色
setColor(sib, BLACK);//将兄弟节点涂黑
setColor(parentOf(x), RED);//父节点涂红
rotateLeft(parentOf(x));//对父节点进行左旋
sib = rightOf(parentOf(x));//更新兄弟节点,仍然是父节点的左子节点,但并不是之前的兄弟节点,因为经过旋转了
}
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {//情况2:兄弟节点为黑色,且兄弟节点的两个子节点都为黑色
setColor(sib, RED);//兄弟节点涂红
x = parentOf(x);//x指向父节点
} else {
if (colorOf(rightOf(sib)) == BLACK) {//情况3:兄弟节点为黑色,且兄弟节点的右子节点为黑色,左子节点为红色
setColor(leftOf(sib), BLACK);//将兄弟节点的左子节点涂黑
setColor(sib, RED);//兄弟节点涂红
rotateRight(sib);//对兄弟节点进行右旋
sib = rightOf(parentOf(x));//更新兄弟节点
}
//情况4:兄弟节点为黑色,且兄弟节点的右子节点为红色
setColor(sib, colorOf(parentOf(x)));//将兄弟节点涂为父节点的颜色
setColor(parentOf(x), BLACK);//父节点涂黑
setColor(rightOf(sib), BLACK);//兄弟节点的右子节点涂红
rotateLeft(parentOf(x));//对父节点进行左旋
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);
}
情况1(兄弟节点为红色):
兄弟节点涂黑,父节点涂红,对父节点进行左旋,更新兄弟节点,如下(R为当前节点x):
对P左旋(现在转换到情况2):
情况2(兄弟节点为黑色,兄弟节点的两个子节点也都为黑色):
将兄弟节点涂红,x指向x的父节点(在图中,R即为x)
情况3:(兄弟节点为黑色,兄弟节点的右子节点为黑色,左子节点为红色)
将兄弟节点的左子节点涂黑,兄弟节点涂红,对兄弟节点进行右旋,对下图做一点解释,我觉得下图这样画不太对劲(图是我从网上找的),不满足黑节点相同的规则,我觉得这里吧SR当成空节点来看更好(空节点默认为黑色)。
可以看到情况3处理之后一定会转换成情况4,代码的逻辑也是情况3之后接着情况4处理。
情况4:(兄弟节点为黑色,兄弟节点的右子节点为红色):
将兄弟节点的右子节点涂黑,兄弟节点的颜色变为父节点的颜色,父节点涂黑,然后对父节点左旋(在图中,R即为x):
可以看到,对情况4进行处理之后,就可以直接删除了。
下面找一个删除之后需要旋转3次的例子过一遍,这个例子我想了很久没想出来,是在网上找的,而且这个红黑树是根据一棵现有的红黑树变色之后得到的,不管那么多了,反正它符合所有红黑树的规则,就拿它来讲解红黑树的删除吧:
初始状态:
可以看到上述状态满足情况2,那么执行情况2的几个步骤,将节点27涂红,x指向20,结果如下:
可以看到上述状态满足情况1,那么执行情况1的几个步骤,将400涂黑,35涂红,对35进行左旋,结果如下:
可以看到上述状态满足情况3,执行情况3的几个步骤,将70涂黑,105涂红,对105进行右旋,结果如下:
可以看到上述状态满足情况4,执行情况4的几个步骤,将105涂黑,70涂红,35涂黑,对35进行左旋,结果如下:
在上述状态的情况下,删除10,发现满足红黑树的所有规则,结束。
最后,总结一下删除时需要调整的四种情况(删除的节点为黑色,当前节点为x,x为父节点的左子节点):
情况1:兄弟节点为红色
步骤:兄弟节点涂黑,父节点涂红,对父节点进行左旋
情况2:兄弟节点为黑色,兄弟节点的两个子节点为黑色
步骤:兄弟节点涂红,将x指向父节点
情况3:兄弟节点为黑色,兄弟节点的右子节点为黑色,左子节点为红色
步骤:左子节点涂黑,兄弟节点涂红,对兄弟节点进行右旋
情况4:兄弟节点为黑色,兄弟节点的右子节点为红色
步骤:右子节点涂黑,兄弟节点涂为父节点的颜色,父节点涂黑,对父节点进行左旋
执行情况3必然进入情况4(源码逻辑也是如此)
对称的四种情况省略
再回顾一下插入时需要调整的三种情况(当前节点为x,x和其父节点都为红色,x的父节点为祖父节点的左子节点):
情况1:叔叔节点为红色
步骤:将父节点和叔叔节点涂黑,祖父节点涂红,将x指向祖父节点
情况2:叔叔节点为黑色,x为其父节点的右子节点
步骤:将x指向父节点,对x进行左旋
情况3:叔叔节点为黑色,x为其父节点的左子节点
步骤:将父节点涂黑,祖父节点涂红,对祖父节点进行右旋
执行情况2必然进入情况3(源码逻辑也是如此)
对称的三种情况省略
整个红黑树的精华就在于上面两段话,一定要理解记忆,不然根本记不住,虽然内容不多。
参考
https://www.jianshu.com/p/e136ec79235c
https://blog.csdn.net/appleyuchi/article/details/70890766