老话说一说,本人菜鸡,如果文章中有错误请大家批评指出!!!
该系列已经全部更完,有5篇文章:
【算法】红黑树(二叉树)概念与查询(一):https://blog.csdn.net/lsr40/article/details/85230703
【算法】红黑树插入数据(变色,左旋、右旋)(二):https://blog.csdn.net/lsr40/article/details/85245027
【算法】红黑树插入数据的情况与实现(三):https://blog.csdn.net/lsr40/article/details/85266069
【算法】红黑树删除数据(寻找继承人)(四):https://blog.csdn.net/lsr40/article/details/85322371
【算法】红黑树删除数据(最后一步,平衡红黑树)(五):https://blog.csdn.net/lsr40/article/details/86711889
删除节点确实比添加节点更为复杂
而且最可怕的是,在我学习添加节点的时候,很多文章我觉得写的有些些乱,然鹅,我在删除节点的时候,情况更多,我看得更乱了,所以我还是秉持着,一边学习概念和方法,一边看看java源码中是如何来处理这些操作的!所以本文也会以java的TreeMap源码为主,来解释下如何处理红黑树删除后的平衡!
备注:不同文章,对于情况的分类可能会有所不同,希望大家还是结合源码来整理自己的思路!
1、首先有两种情况是不需要平衡的,比如删除的是红色节点,删除的是根节点,所以这两种情况我们就可以排除掉了
2、删除的节点是黑色的,那就会导致删除的那条分支少一个黑色节点!
因此我们就进入了平衡
情况1:删除的节点是黑色,他的兄弟节点是红色(因为兄弟节点是红色,所以P点不可能是红色)
情况1-1:被删除的子节点没有孩子
SL和SR肯定是有黑色节点的,否则删除前就不平衡了。
删除前,左边是两个黑色节点,右边也是两个,删除后平衡后,左边还是两个,右边也两个(其实做的操作就是把兄弟的孩子借过来了)
情况1-2:被删除的子节点有孩子(只可能是有一个孩子,并且是右孩子)
首先说下,为什么说只可能有一个孩子,因为要删除的点P,在是否寻找找继承人的时候,需要做判断,如果点P有两个孩子,那就找继承人,继承人是点P右子树中最小的,也就是说,如果继承人有子节点,那只可能是有右孩子,因为如果是有左孩子的话,那左孩子才会是继承人(才会是右子树中最小的那个)!
你会发现,删除前左边3个黑色节点(因此右边应该也是3个,我没有具体画出来),平衡后,似乎还是没有满足红黑树的规则,别急,重新设置叔叔节点为SL,进入到下一层迭代
总结下:所以情况1下面的两种情况,处理方式都是一样的,都会把SL当成新的叔叔,只是没有孩子的情况就不需要继续处理了(因为平衡了),有孩子的情况还得继续往下处理
情况2:删除的节点的兄弟节点是黑色(因为D和S都是黑色,所以无法确定P点的颜色,但是P的颜色不影响整体)
如果兄弟节点是黑色,那就得看兄弟节点的孩子的情况了
情况2-1:兄弟节点有两个黑色的孩子(NIL也算黑色)(这里其实也分D有没有孩子,但是其实处理是一样的,所以就不画出来了)
我们知道左边删了一个节点,少了一个黑色,那只要右边拿过来一个,或者右边直接少一个黑色节点就可以了,因此这里的做法是右边少一个黑色节点(直接把兄弟变成红色),然后进入下一次迭代
注:进入下一次迭代,删除的节点就变成P点(x = parentOf(x);),如果P是红色,就违背了红黑树的规则,因此源码中有最后一行(setColor(x, BLACK);),如果P是黑色,那就继续迭代判断
情况2-2-1:否则(并不是两个孩子都为黑色),如果右孩子是黑色,那左孩子就是红色了(因为我先判断了两个孩子是否为黑色)
当做完这一切之后,让SL变成新的兄弟节点,然后进入到情况1-2-3的处理中!
问题:发现了吗,如果红色在左孩子上,我们要做的就是把红色节点转到右孩子?具体为什么,下一种情况我会告诉大家,大家接着往下看
情况2-2-2:如果左孩子是黑色,那右孩子就是红色了
解答:上一个情况我问的问题,为什么红色要在右孩子上(因为左孩子,会被转到左边去,你看SL节点),这里的操作其实是,P节点要转到左边去平衡左边删除的黑色节点,然而S节点需要变成P节点的颜色来代替P节点,那么原来的右边就少了一个黑色,因此需要一个原来是红色的节点来平衡右边,所以红色节点必须在右孩子上!!
源码如下:
//TreeMap类的2301行开始
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,就是情况1-1-2,有孩子的情况
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,就是情况1-1-1,没有孩子的情况
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;
}
}
}
/** From CLR */
private void fixAfterDeletion(Entry<K,V> x) {
while (x != root && colorOf(x) == BLACK) {
//这里判断传入的节点是左孩子还是右孩子,
//是因为在左在右他的处理方式刚好是镜面对称
//所以得分开处理
if (x == leftOf(parentOf(x))) {
//拿到兄弟节点
Entry<K,V> sib = rightOf(parentOf(x));
//情况1-1!!兄弟节点如果是红色
if (colorOf(sib) == RED) {
//兄弟节点变黑
setColor(sib, BLACK);
//父亲变红
setColor(parentOf(x), RED);
//根据父亲节点左旋
rotateLeft(parentOf(x));
//重新给定兄弟节点
sib = rightOf(parentOf(x));
}
//进入到了情况1-2
//如果情况是1-2-1,那么证明没有红色节点,
//那就直接兄弟节点变红色
//这样左边就少了一个黑色,就完成了平衡
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
setColor(sib, RED);
//x就变成他的父节点,进入到下次迭代
//如果他父节点本身就是红色,就会不进入循环直接变成黑色
x = parentOf(x);
//否则就是有红色节点
} else {
//判断是否属于情况1-2-2
//红色节点在左边,就要加一步处理,把红色节点换到右边
if (colorOf(rightOf(sib)) == BLACK) {
//设置左孩子也为黑色
setColor(leftOf(sib), BLACK);
//兄弟节点为红色
setColor(sib, RED);
//根据兄弟节点右旋
rotateRight(sib);
//重新设置兄弟节点
sib = rightOf(parentOf(x));
}
//红色节点在右边,情况属于1-2-3
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);
}
总结:
情况1:兄弟节点为红色
情况1-1:父亲节点变红转到左边(为了来弥补左边的空缺),叔叔节点变黑,代替原来的父亲节点
情况1-2:处理同上,只是一个需要处理完已经平衡了,一个还没有
情况2:兄弟节点为黑色
情况2-1:两个孩子都是黑色(证明,孩子没有红色节点,Nil也是黑色),直接叔叔变红色,结束
情况2-2-1:左孩子是红色,就把左孩子旋转到右边,让右孩子变红色,重新设置叔叔节点,然后进入情况2-2-2的处理
情况2-2-2:右孩子是红色,让父亲节点去弥补左边黑色节点的空缺,让叔叔来代替父亲节点,让原本是红色的右孩子,来代替叔叔的黑色
终于是把红黑树系列更新完了,拖得太久,拖得我头疼。。。不过确实12月底到1月份很忙!所以更新得也比较慢,希望我的下一篇文章会来的更早一些~