【算法】红黑树删除数据(最后一步,平衡红黑树)(五)

老话说一说,本人菜鸡,如果文章中有错误请大家批评指出!!!

 

该系列已经全部更完,有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源码为主,来解释下如何处理红黑树删除后的平衡!

备注:不同文章,对于情况的分类可能会有所不同,希望大家还是结合源码来整理自己的思路!

在此之前,我们先回顾下上一篇文章的deleteEntry方法,在执行删除的时候,会调用该方法,该方法里又调用了fixAfterDeletion来平衡红黑树,对于删除的节点是否有孩子,deleteEntry方法的处理方式是不太一样的,如果有2个孩子,就找继承人,找到的继承人肯定是没有孩子,或者只有一个右孩子!

首先说下,为什么说只有一个右孩子,因为要删除的点P,在是否寻找找继承人的时候,需要做判断,如果点P有两个孩子,那就找继承人,继承人是点P右子树中最小的,也就是说,如果继承人有子节点,那只可能是有右孩子,因为如果是有左孩子的话,那左孩子才会是继承人(才会是右子树中最小的那个)!

如果没有孩子,就先进入旋转的代码,然后再断开要删除的节点的相关连接,如果有一个孩子,那就先删除节点,然后用被删除的节点的子节点代替被删除节点,进入到旋转中,也就是说在旋转的时候D节点会一直存在!当然这不会影响我们的旋转~

1、首先有两种情况是不需要平衡的,比如删除的是红色节点,删除的是根节点,所以这两种情况我们就可以排除掉了

2、删除的节点是黑色的,那就会导致删除的那条分支少一个黑色节点!

因此我们就进入了平衡

情况1:删除的节点是黑色,他的兄弟节点是红色(因为兄弟节点是红色,所以P点不可能是红色,而且SL和SR也不可能是红色,所以经过情况1的变换之后,新的兄弟节点(SR)肯定是黑色的)

SL和SR肯定是有黑色节点的,否则删除前就不平衡了。

删除后,明显是不平衡的(S-P-NIL,只有2个黑,其他线都是3个黑),需要进入情况2继续判断(这时候设置新的兄弟节点为SL),进行下一步处理

情况2-1:兄弟节点两个子节点都为黑色(经过情况2-1处理后,就平衡了!)

这个其实很好理解,就是兄弟节点为黑色,然后自己被删了,左边少了黑色,就把同级的兄弟节点变成红色,因为兄弟节点的子节点又全部都是黑色,所以不会有影响后面

你可以接着上面的图,往下看(SL变成新的兄弟节点)

 

那左边删除了一个D节点,就少了一个黑色,右边直接把删除的节点的兄弟节点变红,这样右边也少一个黑色节点,平衡了

注意:P点(父节点)的变红是在源码的最后一行代码中处理的,

先将父节点赋值给X(代码:x = parentOf(x);),再把父节点P变为黑(代码:setColor(x, BLACK);)

 

情况2-2:否则(并不是两个孩子都为黑色),如果右孩子是黑色,那左孩子就是红色了(因为我先判断了两个孩子是否为黑色)(因为D和S都是黑色,所以无法确定P点的颜色,但是这种情况P的颜色不影响整体)

 

当做完这一切之后,让SL变成新的兄弟节点,然后进入到情况2-3的处理中!

问题:发现了吗,如果红色在左孩子上,我们要做的就是把红色节点转到右孩子?具体为什么,下一种情况我会告诉大家,大家接着往下看

情况2-3:如果左孩子是黑色,那右孩子就是红色了

解答:上一个情况我问的问题,为什么红色要在右孩子上(因为左孩子,会被转到左边去,你看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:兄弟节点为红
                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));
                    sib = rightOf(parentOf(x));
                }
                //情况2-1:由于经过上面的处理,兄弟节点肯定为黑色
                //这时,如果兄弟节点两个子节点都为黑
                if (colorOf(leftOf(sib))  == BLACK &&
                        colorOf(rightOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    //情况2-2:兄弟节点右孩子是黑,左孩子是红,那就需要先把红色孩子转到右边
                    if (colorOf(rightOf(sib)) == BLACK) {
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    //情况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:父亲节点变红转到左边(为了来弥补左边的空缺),兄弟节点变黑,代替原来的父亲节点,重新设置兄弟节点(这时候兄弟节点肯定是黑色的了),继续对新的情况做分析(会跳到情况2进行分析)

情况2:兄弟节点为黑色

           情况2-1:兄弟节点是不是两个子节点都是黑色,两个孩子都是黑色(证明,孩子没有红色节点,Nil也是黑色),直接兄弟变红色,然后原来的父节点变黑,平衡结束

情况2-2:左孩子是红色,就把左孩子旋转到右边,让右孩子变红色,重新设置兄弟节点,然后进入情况2-3的处理

情况2-3:右孩子是红色,让父亲节点去弥补左边黑色节点的空缺,让兄弟来代替父亲节点,让原本是红色的右孩子,来代替兄弟的黑色,平衡结束

 

终于是把红黑树系列更新完了,拖得太久,拖得我头疼。。。不过确实12月底到1月份很忙!所以更新得也比较慢,希望我的下一篇文章会来的更早一些~

本人菜鸡一只,如果有什么说错的地方,请大家批评指出,坚决不乱复制别人的文章,并且不写误导新人的文章!!

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值