跟着TreeMap学红黑树之终结篇

今天总结一下红黑树的删除以及删除之后的调整过程,还是基于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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值