红黑树学习 java实现简单的红黑树(2)

上文: 红黑树学习 java实现简单的红黑树(1)

红黑树学习 java实现简单的红黑树

remove

传入要删除的值

public Integer remove(Integer val) {
        // 找到值为val的结点
        Node p = getNode(val);
        // 找不到则直接返回null
        if (p == null) return null;

        Integer oldValue = p.value;
        // 删除找到的结点
        deleteNode(p);
        return oldValue;
    }

deleteNode

用二叉搜索树的删除方法, 因为红黑树实际上是二叉搜索树的变形
删除的结点有三种情况

  1. p有两个孩子, 那么从右孩子中找到最小的结点与p值交换, 删除这个最小结点, 因为是二叉搜索树, 所以这个结点一定是右孩子中最左的孩子, 所以他没有左孩子, 可以进入情况2
  2. p只有一个孩子, 直接将这个孩子与p的父亲进行连接操作, 操作完成后, 如果p是黑色则调整红黑树
  3. p没有孩子, 直接将p删除, p如果是黑色, 那就调整红黑树

因为如果p是红色, 就不会改变红黑树的稳态, 所以无需调整红黑树, 如果是黑色, 那么在路径上该子树就会比兄弟子树的黑色结点数目少1, 要进行维稳

    public void deleteNode(Node p) {
        size--;

        // p有两个孩子, 从右孩子中找到最小的值交换
        if (p.left != null && p.right != null) {
            Node s = minInRight(p);
            p.value = s.value;
            p = s;
        }

        // 需要删除的结点, 若与其他结点交换过, 那么当前p一定没有左孩子
        Node replacement = (p.left == null ? p.right: p.left);
        // 由上所得, replacement如果不是空, 那么一定是p的右孩子或者左孩子
        // 如果p是黑色, 那么p的左孩子或者右孩子如果是个黑色有值结点就会破坏红黑树
        if(replacement != null) {
            //要删除p, 所以将p唯一一个孩子与p的父亲连接
            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;
            }

            p.left = p.right = p.parent = null;
            // 如果删除的结点是黑色, 那么会破坏红黑树的稳态, 所以进行调整
            // 这里的操作是直接变黑, 因为p是黑色的话replacement一定是红色
            if(p.color == BLACK) fixAfterDel(replacement);
        } else if(p.parent == null) {
            // p是红黑树中的惟一一个结点
            root = null;
        } else {
            // p没有孩子, 直接将自己删除
            // 如果p是黑色, 进行调整
            if(p.color == BLACK) {
                fixAfterDel(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;
            }
        }
    }

这里是找到可以替换删除的结点的函数

    static Node minInRight(Node t) {
        if(t == null) return null;
        if(t.right == null) return null;
        // t的右孩子不为空, 找到右孩子中最小的结点, 即找到右孩子中最左的孩子
        Node p = t.right;
        while (p.left != null) p = p.left;
        return p;
    }

fixAfterDel

调整删除结点后的红黑树
这里的几种情况可以参考这个链接红黑树删除图解
我这里引用一张图
Alt
总而言之就是把所有的情况都转为2.2.1

 private void fixAfterDel(Node x) {
        // x不是根节点并且是黑色就进行循环
        // 这里如果replacement是红色的, 直接变黑就可以保持稳态
        while(x != root && colorOf(x) == BLACK) {
            if(x == leftOf(parentOf(x))) {
                // 由于上面删除了p, 所以x树的黑结点比bro少1
                Node bro = rightOf(parentOf(x));

                // 如果兄弟是红色, 就用旋转把兄弟变黑, 并且保持少1状态
                // 因为兄弟是红色, 所以爹一定是黑色
                if(colorOf(bro) == RED) {
                    setColor(bro, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));
                    // 此时的x是转过来的爹, bro是原来bro的右孩子
                    bro = rightOf(parentOf(x));
                }

                // 到了这里bro一定是黑色
                // 如果bro的两个孩子都是黑色, 只需要将bro变红
                // 然后判断爹, 如果爹是红色直接跳出循环变黑就行
                // 否则以爹为入参重新调整, 因为bro变红之后, 这一棵子树的黑结点少了1
                if(colorOf(rightOf(bro)) == BLACK && colorOf(leftOf(bro)) == BLACK) {
                    setColor(bro, RED);
                    x = parentOf(x);
                } else { // 全红或者一红一黑
                    // 如果右侄子是黑色, 那么进行右旋, 将新的右侄子变为红色
                    if(colorOf(rightOf(bro)) == BLACK) {
                        // 将左侄子变黑, 兄弟变红然后右旋
                        // 完成之后, 兄弟还是黑色, 原来的红色兄弟变成右侄子
                        setColor(leftOf(bro), BLACK);
                        setColor(bro, RED);
                        rotateRight(bro);
                        bro = rightOf(parentOf(x));
                    }
                    // 下面是对右侄子为红色, 左侄子为黑色或者全红的处理
                    // 经过上一步之后, 保证了右侄子不是黑色
                    // 为了维稳, 必须要让x树多一个黑色结点
                    // bro将要变为爹, 所以让bro保持爹的颜色, 只需将左右子树的黑色维稳
                    setColor(bro, colorOf(parentOf(x)));
                    // 最后bro会变成爹, 爹变成左子树, 由于原来的bro是黑色
                    // 所以让将会变成bro的bro的右孩子涂黑
                    // 这样左边多了一个黑色结点, 右边的黑色结点保持不变
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(bro), BLACK);
                    rotateLeft(parentOf(x));
                    // 跳出循环
                    x = root;
                }
            } else { // 对称操作
                Node bro = leftOf(parentOf(x));

                if(colorOf(bro) == RED) {
                    setColor(bro, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    bro = parentOf(x);
                }

                if(colorOf(rightOf(bro)) == BLACK && colorOf(leftOf(bro)) == BLACK) {
                    setColor(bro, RED);
                    x = parentOf(x);
                } else {
                    if(colorOf(leftOf(bro)) == BLACK) {
                        setColor(rightOf(bro), BLACK);
                        setColor(bro, RED);
                        rotateLeft(bro);
                        bro = leftOf(parentOf(x));
                    }
                    setColor(bro, colorOf(parentOf(x)));
                    setColor(leftOf(bro), BLACK);
                    setColor(parentOf(x), BLACK);
                    rotateRight(parentOf(x));
                    x = root;
                }
            }
        }
        // 将x设为黑色, 这里有两种情况
        // 1. x是红色, 这里变黑之后保持了黑结点的稳态
        // 2. x是根节点, 根节点本来就是黑色的, 再变黑一次也没关系
        setColor(x, BLACK);
    }

后记

其实这里的代码与TreeMap的源码基本一样, 只是将Node变成了简单的结点而不是键值对, 可以说这就是对TreeMap的源码学习吧

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值