java8 TreeMap红黑树节点删除源码解析

      红黑树添加节点的平衡操作相对简单,具体分析参考java8 TreeMap接口实现源码解析,红黑树节点删除需要考虑的情形和翻转动作更复杂。综合各博客大神对该问题的分析,以TreeMap中红黑树节点删除相关代码为例,记录下自己对该问题的分析和理解,欢迎指正。

一、红黑树的规则

1、每个节点都只能是红色或者黑色

2、根节点是黑色

3、每个叶节点(NIL节点,空节点)是黑色的。

4、如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。

5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点,即相同的黑色高度。

重点说明下,所谓的叶节点就是空节点,必须是黑色的,叶节点的父节点可能是红色的,也可能是黑色的。

二、红黑树节点删除整体分析

先不考虑节点颜色问题,被删除的节点可能存在以下四种可能:

1、左右子树为空

扫描二维码关注公众号,回复: 6206456 查看本文章

2、左子树存在,右子树为空

3、左子树为空,右子树存在

4、左右子树都非空

遇到第四种情形时,处理技巧是先找到比目标节点D大的最小节点即D节点右子树中最左边的一个节点,或者比D小的最大的节点即D节点左子树中最右边的一个节点,记为X节点,将X节点的值复制到D节点上,然后删除X节点。这样处理D节点的左右子树可保持不变,而X节点因为要么是最左边要么是最右边的一个节点,所以X节点的左右子树肯定都为空,即情形四转换成了情形一,旋转操作相对简单。即实际处理时只需要考虑前面三种情形即可,下面一一分析。

三、红黑树节点删除具体情形分析

1、只有左子树或者右子树存在,即情形二和情形三

     因为从父节点到所有的叶子节点的黑色高度必须一致,所以此种情形下,被删除节点D的左子树或者右子树只能是一个红色节点,而D节点只能是黑色的,如下图所示,因为如果D节点为红色的,则D节点的左子树或者右子树必须是黑色的,就违背了黑色高度一致原则,如果D节点是黑色的,DR节点也是黑色的,同样违背了黑色高度一致原则。

      这种情形的处理最简单,删除D节点,用D节点的DR节点代替D节点,并把DR节点涂黑即可。

2、左右子树都为空,即情形一

1)、被删除节点D是红色的

因为删除红色节点不改变该节点所在路径的黑色高度,所以可以直接删除。

2)、被删除节点是黑色的

删除黑色节点肯定会改变该节点所在路径的黑色高度,肯定需要通过左旋或者右旋调整,因为被删除节点D左右子树都为空,所以只能往上考虑D节点的父节点P和兄弟节点S了。同红黑树节点插入的平衡操作,D节点是左节点和D节点是右节点时的旋转方向是相反的,过程是一样的,这里以D节点是左节点为例分析。

     2a)兄弟节点S是红色的

 兄弟节点S是红色的,S的左节点SL和右节点SR一定是黑色的,SL和SR不为空节点的话则下面最多还有一个红色的子节点。这种情形的处理是对P节点左旋,然后把S节点涂黑,P节点涂红,从而将兄弟节点S是红色的转换成兄弟节点是黑色的情形,如下图所示:

     2b)兄弟节点S是黑色的

      这种情形最为复杂,需要考虑P节点的颜色和SL,SR节点的颜色,这里分三种情形处理:

      1、SL和SR都是黑色节点,P节点的颜色为红色

       SL和SR都是黑色节点的话,则SL和SR必为黑色的空节点。这种情形的处理是将S节点涂红,P节点涂黑,然后删除D节点即可,从P节点往各叶子节点的黑色高度不变,如下图所示:

      

   2、SL为红色,SR为黑色,P节点的颜色任意

    SR是黑色节点则必为空节点,这种情形的处理是将SL涂黑,S节点涂红,然后对S节点右旋,从而将这种情形转换成了下面的情形三。

 3、SR为红色,SL为黑色,P节点颜色任意

  SL节点为黑色,则SL必为空节点。这种情形的处理是将S节点涂成P节点的颜色,P节点涂黑,SR节点涂黑,然后对P节点左旋。这种情形下从S节点出发到所有的叶子节点的黑色节点数与原来的P节点一致,达到平衡,可以放心删除D节点。

总结上面讨论的被删除节点是黑色的四种情形,可以发现其旋转的最终效果是保证根节点到D节点的黑色高度加1了,而到其他叶子节点的高度不变,这样删除D节点后,根节点到所有叶子节点的高度都一致了。最重要的一点,上面三种情形的旋转只关心D节点是黑色的,无需考虑D节点是否有左子树右子树。 以最后一种情形为例,如果D节点下面有一个黑色节点,则SL必为黑色的非空节点,SR下面还有一个非空的黑色节点,因为旋转操作不影响D,SL,SR节点同其子节点的关系,所以旋转完成后到D节点的黑色高度依然会加1,如下图所示:

4、SL和SR都是黑色节点,P节点的颜色为黑色

    SL和SR都是黑色节点,则必为空节点。这时因为P节点下左子树和右子树都是黑色节点了,如果删除D节点,无法对P节点及其子节点做旋转来保证黑色高度一致,只能通过P节点的父节点和兄弟节点的旋转来保持平衡了。这时的处理方法时,将S节点涂红,然后将P节点作为待删除的黑色节点,走被删除节点是黑色的逻辑。S涂红是为了让从P节点到SL,SR的黑色高度减1,后面对P节点做平衡处理,根节点到P节点的黑色高度会加1,从而保证根节点到SL,SR节点的黑色高度保持不点,而根节点到D节点的黑色高度会加1,从而可以安全删除D节点。如下图所示,假如P节点的兄弟节点为红色的,则采用相同的旋转方式,即PP节点涂红,PS节点涂黑,对PP节点坐旋转,从而将其变为P节点的兄弟节点为黑色的情形,后面的旋转动作参考上面的分析。

四、TreeMap删除节点源码解析

private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;

        // 节点p的左右节点都存在,将p节点的下一个节点s,即大于P的最小节点,即P的右子树中最左边的一个节点,将s中的值拷贝到p中,然后删除s
        //此时s最多有一个右节点
        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

        // replacement表示替代被删除节点的节点,p节点被删除了,只能用其子节点代替
        // 经过上面的转换,p要么没有子节点,要么只有一个子节点
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        //p有一个子节点
        if (replacement != null) {
            // 设置replacement的父节点
            replacement.parent = p.parent;
            if (p.parent == null)//父节点为空,replacement为根节点
                root = replacement;
            else if (p == p.parent.left)//p为其父节点的左节点,replacement代替p
                p.parent.left  = replacement;
            else
                //p为其父节点的右节点,replacement代替p
                p.parent.right = replacement;

            //将p节点的引用置空
            p.left = p.right = p.parent = null;

            //这种情形p节点就是黑色的,子节点即replacement必须是红色的,fixAfterDeletion实际只是将replacement涂黑
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { //无替换节点且p为根节点,直接删除
            root = null;
        } else {
            //无替换节点,即p无子节点,注意此处是先调整红黑树,再删除p节点,如果先删除再调整就找不到p节点的父节点了,无法调整
            //如果p是黑色节点需要调整红黑树,此时需要考虑p的兄弟节点以及p的位置,如果是红色节点则可以直接删除
            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;
            }
        }
    }

    /** From CLR */
    private void fixAfterDeletion(Entry<K,V> x) {
        while (x != root && colorOf(x) == BLACK) {
            //x是左节点
            if (x == leftOf(parentOf(x))) {
                Entry<K,V> sib = rightOf(parentOf(x));

                //兄弟节点是红色的,
                if (colorOf(sib) == RED) {
                    //兄弟节点涂黑
                    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指向父节点,此时父节点为红色则跳出循环,最后将其涂黑,达到平衡
                    //如果父节点是黑色则继续做平衡处理,保证到P节点的黑色高度加1
                    x = parentOf(x);
                } else {
                    //兄弟节点的左节点为红色的,右边是黑色的
                    if (colorOf(rightOf(sib)) == BLACK) {
                        //兄弟节点的左节点涂黑,此时兄弟节点的左右节点都是黑色
                        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跳出循环,此时x原来的引用并没有改变
                    x = root;
                }
            } else { //x是右节点,与左节点逻辑相同,旋转方向相反
                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;
                }
            }
        }
        //x为root或者红色节点,此时统一置成黑色
        setColor(x, BLACK);
    }


 

     

猜你喜欢

转载自blog.csdn.net/qq_31865983/article/details/86659314