【手撕红黑树 | 史上最详细注解】增删查改 原理剖析 代码实现

1、基本特征

红黑树起源于2-3-4树(或2-3树),底层是二叉查找树,除二叉树的特性外,还有5大特性:

  • 根是黑色

  • 节点是黑色或红色

  • 叶子节点都是黑色

    叶子节点为NIL节点,不可忽略,见下图

  • 每个红色节点的两个子节点必须是黑色

    从每个叶子到根的路径上不能有两个连续的红节点

  • 黑色平衡

    从任一节点到其所有叶子节点的简单路径上黑色节点数量相同

在这里插入图片描述

2、内部节点类Node

双向链表:父 > 子 子 > 父

    /**
     * 内部节点类
     */
    private class Node {
        /**
         * 键 key
         */
        private K key;
        /**
         * 值 value
         */
        private V value;
        /**
         * 父节点
         */
        private Node parent;
        /**
         * 左子节点
         */
        private Node left;
        /**
         * 右子节点
         */
        private Node right;
        /**
         * 节点颜色
         * 红色:true
         * 黑色:false
         */
        private boolean color;

        /**
         * 构造器
         *
         * @param key
         * @param value
         * @param parent
         * @param left
         * @param right
         */
        public Node(K key, V value, Node parent, Node left, Node right) {
            this.key = key;
            this.value = value;
            this.parent = parent;
            this.left = left;
            this.right = right;
            this.color = BLACK;
        }
    }

3、成员变量与构造器

/**
 * @author 土味儿
 * Date 2021/9/10
 * @version 1.0
 * 红黑树
 */
public class RedBlackTree1<K extends Comparable<K>, V> {
    /**
     * 根节点
     */
    private Node root;
    /**
     * 元素数量
     */
    private int n;
    /**
     * 红色
     */
    private static final boolean RED = true;
    /**
     * 黑色
     */
    private static final boolean BLACK = false;

    /**
     * 构造器
     */
    public RedBlackTree1() {
        this.n = 0;
    }
}    

4、基础方法

4.1、元素数量 size()

public

    /**
     * 元素数量
     *
     * @return
     */
    public int size() {
        return n;
    }

4.2、节点颜色 colorOf()

private

    /**
     * 节点颜色
     *
     * @param x
     * @return
     */
    private boolean colorOf(Node x) {
        if (x != null) {
            return x.color == RED;
        }
        return BLACK;
    }

5、查询

5.1、get()

public

    /**
     * 得到 key 结点的值
     *
     * @param key
     * @return
     */
    public V get(K key) {
        Node node = getNode(key);
        return node != null ? node.value : null;
    }

5.2、getNode()

private

   /**
     * 获取 key 节点
     *
     * @param key
     * @return
     */
    private Node getNode(K key) {
        // 空树、空键
        if (root == null || key == null) {
            return null;
        }

        // 从根结点开始查找
        Node node = root;
        // 比较 key 与 h 键的大小
        int cmp;
        // 循环遍历
        while (node != null) {
            cmp = key.compareTo(node.key);
            if (cmp < 0) {
                node = node.left;
            } else if (cmp > 0) {
                node = node.right;
            } else {
                return node;
            }
        }
        return null;
    }

5.3、树根

public

    /**
     * 得到树根的value
     * @return
     */
    public V getRoot(){
        return root.value;
    }

5.4、最小

  • getMin()

    public

    /**
     * 最小值
     * @return
     */
    public V getMin() {
        Node minNode = getMinNode();
        return minNode != null ? minNode.value : null;
    }
  • getMinNode()

    private

    /**
     * 最小节点
     * 最左侧叶子
     *
     * @return
     */
    private Node getMinNode() {
        Node p = root;
        if (p != null) {
            while (p.left != null) {
                p = p.left;
            }
        }
        return p;
    }

5.5、最大

  • getMax()

    public

    /**
     * 最大值
     *
     * @return
     */
    public V getMax() {
        Node maxNode = getMaxNode();
        return maxNode != null ? maxNode.value : null;
    }
  • getMaxNode()

    private

    /**
     * 最大节点
     * 最右侧叶子
     *
     * @return
     */
    private Node getMaxNode() {
        Node p = root;
        if (p != null) {
            while (p.right != null) {
                p = p.right;
            }
        }
        return p;
    }

5.6、前驱节点

private

小于当前节点的最大节点

就是当前节点左子树中的最右节点;在左子树中循环向右,最后一个

/**
     * 前驱节点
     * 小于 t 的最大节点
     * t 左子树中最右节点
     *
     * @param t
     * @return
     */
    private Node predecessor(Node t) {
        // t 为null时,返回null
        if (t == null) {
            return null;
        }

        Node p;
        // t 有左子树:在左子树中找出最右侧节点即可
        if (t.left != null) {
            p = t.left;
            // 向右循环查找
            while (p.right != null) {
                p = p.right;
            }
            return p;
        }
        // t 没有左子树:向父节点查找
        // 在父辈节点中找出最近的左侧节点(t的左侧、最近的父辈节点)
        else {
            p = t.parent;
            Node ch = t;
            // 如果父辈在右侧就循环查找
            while (p != null && ch == p.left) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

5.7、后继节点

private

大于当前节点的最小节点

就是当前节点右子树中的最左节点;在右子树中循环向左,最后一个

/**
     * 后继结点
     * 大于 t 的最小值
     * t 右子树中最左叶子结点
     *
     * @param t
     * @return
     */
    private Node successor(Node t) {
        // t 为null时,返回null
        if (t == null) {
            return null;
        }

        Node p;
        // t 有右子树:在右子树中找出最左侧节点即可
        if (t.right != null) {
            p = t.right;
            // 向左循环查找
            while (p.left != null) {
                p = p.left;
            }
            return p;
        }
        // t 没有右子树:向父节点查找
        // 在父辈节点中找出最近的右侧节点(t的右侧、最近的父辈节点)
        else {
            p = t.parent;
            Node ch = t;
            // 如果父辈在左侧就循环查找
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

6、修改

public

/**
     * 修改
     * @param key
     * @param value
     * @return
     */
    public int modify(K key,V value){
        // 空树、空键
        if (root == null || key == null) {
            return 0;
        }

        // 从根结点开始查找
        Node node = root;
        // 比较 key 与 h 键的大小
        int cmp;
        // 循环遍历
        while (node != null) {
            cmp = key.compareTo(node.key);
            if (cmp < 0) {
                node = node.left;
            } else if (cmp > 0) {
                node = node.right;
            } else {
                node.value = value;
                return 1;
            }
        }

        return 0;
    }

7、添加

思路:先添加、再调整

  • 新增结点都是红色

  • 都是从叶子节点新增

步骤:

  • 空树时,直接添加
  • 非空树时,查找要添加位置

public

 /**
     * 添加
     * 当 key 存在时即为修改
     * @param key
     * @param value
     */
    public void put(K key, V value) {
        // 空指针
        if (key == null) {
            throw new NullPointerException();
        }

        Node t = root;
        // 空树
        if (t == null) {            
            // 新建节点
            root = new Node(key, value, null, null, null);
            // 数量加1
            n++;
            return;
        }

        // 非空树,查找插入位置

        // 比较值
        int cmp;
        // 父节点
        Node parent;

        // 循环查找插入位置
        do {
            parent = t;
            // 比较 key 与 t 节点的键的大小
            cmp = key.compareTo(t.key);

            // 小于:向左继续查找
            if (cmp < 0) {
                t = t.left;
            }

            // 大于:向右继续查找
            else if (cmp > 0) {
                t = t.right;
            }

            // 等于:值替换
            else {
                t.value = value;
                // 退出循环,退出
                return;
            }

        } while (t != null);

        // 循环正常结束,表示已找到插入位置,就在 parent 的下面,即为 parent 的儿子;此时 t 为null,不能用
        Node e = new Node(key, value, parent, null, null);
        // 左儿子
        if (cmp < 0) {
            parent.left = e;
        }
        // 右儿子
        else {
            parent.right = e;
        }

        // 调整
        fixAfterPut(e);
        // 数量加1
        n++;
        return;
    }

8、添加后调整

8.1、左旋

private

只做旋转,不变色

  /**
     * 左旋
     * -------------------------------------
     *      P                       X
     *    /  \\                   // \
     *   a    X      左旋后>      P    c
     *      /  \               /  \
     *     b    c             a    b
     * -------------------------------------
     * 只做旋转,不做变色
     * @param p
     */
    private void rotateLeft(Node p) {
        // 参数有效性检测
        if (p == null || p.right == null) {
            return;
        }

        // 当前节点为 p,它的右子节点为 x
        Node x = p.right;

        // 左旋后:
        // 1、p、x节点的父亲、儿子都发生了变化
        // 2、x左子节点的父亲发生变化,儿子不变
        // 3、p的父亲的儿子发生变化

        // 更新 x 的左子
        if(x.left != null){
            x.left.parent = p;
        }

        // 更新 p 的右子
        p.right = x.left;

        // 更新 x
        x.parent = p.parent;

        // 更新 p 的父亲
        if(p.parent == null){
            root = x;
        }
        else if(p.parent.left == p){
            p.parent.left = x;
        }
        else{
            p.parent.right = x;
        }

        x.left = p;
        // 更新 p 的父亲
        p.parent = x;
    }

8.2、右旋

private

只做旋转,不变色

    /**
     * 右旋
     * -------------------------------------
     *         P                  X
     *       // \               // \\
     *      X    c    右旋后>   a     P
     *    // \                      / \
     *   a    b                    b   c
     * -------------------------------------
     *
     * @param p
     */
    private void rotateRight(Node p) {
        // 参数有效性检测
        if (p == null || p.right == null) {
            return;
        }

        // 当前节点为 p,它的左子节点为 x
        Node x = p.left;

        // 右旋后:
        // 1、p、x节点的父亲、儿子都发生了变化
        // 2、x右子节点的父亲发生变化,儿子不变
        // 3、p的父亲的儿子发生变化

        // 更新 x 的右子
        if(x.right != null){
            x.right.parent = p;
        }

        // 更新 p 的左子
        p.left = x.right;

        // 更新 x
        x.parent = p.parent;

        // 更新 p 的父亲
        if(p.parent == null){
            root = x;
        }
        else if(p.parent.right == p){
            p.parent.right = x;
        }
        else{
            p.parent.left = x;
        }

        x.right = p;
        // 更新 p 的父亲
        p.parent = x;
    }

8.3、添加后调整分析

1)向2-3-4树中的1节点添加

1节点就是根节点,相当于空树

直接添加,不需要调整

2)向2-3-4树中的2节点添加

2节点就是包含:1个元素,有2个分叉

2节点都是黑色

直接添加,上黑下红;不需要调整

在这里插入图片描述

3)向2-3-4树中的3节点添加

3节点就是包含:2个元素,有3个分叉

在这里插入图片描述

在这里插入图片描述

4)向2-3-4树中的4节点添加

4节点就是包含:3个元素,有4个分叉

在这里插入图片描述

如果爷爷节点为根节点,则变黑

5)分析

  • 节点需要调整的条件

    1. 不为null

    2. 不为根

    3. 父节点为红色

  • 具体分类:

    • 4、5、7、8 需要旋转
    • 10、11、12、13 需要变色
  • 循环操作

6)fixAfterPut()

private

 /**
     * 添加后调整
     * 旋转、变色
     *
     * @param x
     */
    private void fixAfterPut(Node x) {
        // 新结点为红色
        x.color = RED;

        // x不为null,且不为root,父节点为红色时才需要调整
        // 4、5、7、8 需要旋转、变色
        // 10、11、12、13 需要变色
        while (x != null && x != root && x.parent.color == RED) {
            // 爷爷的左侧
            // 【4、5、10、11 四种情况】
            // x的父节点是爷爷的左孩子
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                // 叔叔节点
                Node y = rightOf(parentOf(parentOf(x)));
                // 叔叔是红色:【10、11两种情况】只需要变色
                if(colorOf(y) == RED){
                    // 父亲变黑
                    setColor(parentOf(x),BLACK);
                    // 叔叔变黑
                    setColor(y,BLACK);
                    // 爷爷变红
                    setColor(parentOf(parentOf(x)),RED);
                    // 以爷爷为起点,循环操作
                    x = parentOf(parentOf(x));
                }
                // 【4、5两种情况】旋转、变色
                else{
                    // 【情况5】先左旋,变成4
                    if (x == rightOf(parentOf(x))) {
                        // x 指向父亲
                        x = parentOf(x);
                        // 以父亲为支点左旋
                        rotateLeft(x);
                    }
                    // 【情况4】变色、右旋
                    // 父亲变黑
                    setColor(parentOf(x), BLACK);
                    // 爷爷变红
                    setColor(parentOf(parentOf(x)), RED);
                    // 以爷爷为支点右旋
                    rotateRight(parentOf(parentOf(x)));
                }
            }
            // 爷爷的右侧
            // 【7、8、12、13 四种情况】
            // x的父节点是爷爷的右孩子
            else{
                // 叔叔节点
                Node y = leftOf(parentOf(parentOf(x)));
                // 叔叔是红色:【12、13两种情况】只需要变色
                if(colorOf(y) == RED){
                    // 父亲变黑
                    setColor(parentOf(x),BLACK);
                    // 叔叔变黑
                    setColor(y,BLACK);
                    // 爷爷变红
                    setColor(parentOf(parentOf(x)),RED);
                    // 以爷爷为起点,循环操作
                    x = parentOf(parentOf(x));
                }
                // 【7、8两种情况】旋转、变色
                else{
                    // 【情况8】先右旋,变成7
                    if (x == leftOf(parentOf(x))) {
                        // x 指向父亲
                        x = parentOf(x);
                        // 以父亲为支点右旋
                        rotateRight(x);
                    }
                    // 【情况7】变色、左旋
                    // 父亲变黑
                    setColor(parentOf(x), BLACK);
                    // 爷爷变红
                    setColor(parentOf(parentOf(x)), RED);
                    // 以爷爷为支点左旋
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }

        // 根节点黑色
        root.color = BLACK;
    }

8.4、辅助方法

1、父节点 parentOf()

    /**
     * 获取父节点
     *
     * @param node
     * @return
     */
    private Node parentOf(Node node) {
        return node != null ? node.parent : null;
    }

2、左子节点 leftOf()

    /**
     * 获取左子节点
     *
     * @param node
     * @return
     */
    private Node leftOf(Node node) {
        return node != null ? node.left : null;
    }

3、右子节点 rightOf()

    /**
     * 获取右子节点
     *
     * @param node
     * @return
     */
    private Node rightOf(Node node) {
        return node != null ? node.right : null;
    }

4、设置颜色 setColor()

    /**
     * 设置颜色
     * @param node
     * @param color
     */
    private void setColor(Node node, boolean color) {
        if (node != null) {
            node.color = color;
        }
    }

9、删除

9.1、思路

先删除,后调整

红黑树是平衡树,删除的节点只可能是叶子节点,或者是叶子节点的父节点(单叶子,没有兄弟)

即:叶子层,或叶子的上一层,不会再向上

如果要删除上面层的节点,会先用替代节点替换下来,转换成叶子或叶子的上一层节点

  • 如果是叶子节点,直接删除
  • 如果只有一个子节点,用子叶点替代
  • 如果有两个子节点,需要用替代节点(前驱或后继),转换成上面两种情况

9.2、remove()

public

    /**
     * 删除 key 节点
     * @param key
     * @return
     */
    public V remove(K key){
        // 得到目标节点
        Node p = getNode(key);
        if(p==null){
            return null;
        }

        V oldValue = p.value;
        // 删除
        deleteNode(p);
        return oldValue;
    }

9.3、deleteNode()

private

    /**
     * 删除目标节点 p
     * 只修改指针
     * 旋转、变色用其它方法
     *
     * @param p
     */
    private void deleteNode(Node p) {
        // 数量减1
        n--;
        
        // 如果p有两个孩子:用后继节点替换
        // 有两个孩子对应于2-3-4树中的4节点(4个分叉,每个孩子各2个)
        if (p.left != null && p.right != null) {
            // 后继节点
            Node s = successor(p);
            // 【值传递】用后继节点替换p的key和value;p的left、right、parent指向并不变
            p.key = s.key;
            p.value = s.value;
            // 【引用传递】更新p的指向:指向后继结点;相当于交换位置
            p = s;
        }
        /**
         * 此时有两个孩子的情况已被过滤掉;p是被删除节点,有三种情况:
         * 1、有一个孩子:对应于2-3-4树中的3节点(3个分叉)
         * 2、没有孩子:对应于2-3-4树中的2节点(2个分叉)
         * 3、没有孩子的根节点:对应于2-3-4树中的1节点(1个分叉)
         */

        // ----------针对上面三种情况,开始删除、调整----------

        // 替补节点:就是要填补【被删除节点p】位置的节点
        Node replacement = (p.left != null ? p.left : p.right);

        // 有一个孩子:对应于2-3-4树中的3节点(3个分叉)
        // 黑父亲 + 红独子
        if (replacement != null) {
            // 更新替补节点的父亲:跳过当前父亲,指向父亲的的父亲,因为当前父亲要被删掉
            replacement.parent = p.parent;

            // -----更新p父亲的孩子指针-----
            // p的父亲是null,p是根节点:替补节点成为根
            if (p.parent == null) {
                root = replacement;
            }
            // p是父亲的左孩子
            else if (p == p.parent.left) {
                p.parent.left = replacement;
            }
            // p是父亲的右孩子
            else {
                p.parent.right = replacement;
            }

            // p的父亲和孩子指针更新完毕,开始删除p
            // 把p的三个指针都设为null,从树中脱落,等待GC回收
            p.parent = p.left = p.right = null;

            // 如果p是黑色节点,开始调整
            if (p.color == BLACK) {
                // 【调整情况一:黑父亲 + 红独子(3节点)】
                // 调整的是替补节点:红孩子
                fixAfterDeletion(replacement);
            }
        }
        // 没有孩子的根节点:对应于2-3-4树中的1节点(1个分叉)
        else if (p.parent == null) {
            root = null;
        }
        // 没有孩子:对应于2-3-4树中的2节点(2个分叉)
        else {
            // 被删除节点是黑色,开始调整
            if (p.color == BLACK) {
                // 【调整情况二:无孩子黑节点(2节点)】
                // 调整的是被删除节点,没有替补节点
                fixAfterDeletion(p);
            }

            // 更新【p】和【p的父亲】的指针
            if (p.parent != null) {
                // 更新【p的父亲】的孩子指针
                // p是它父亲的左孩子
                if (p == p.parent.left) {
                    p.parent.left = null;
                }
                // p是它父亲的右孩子
                else if (p == p.parent.right) {
                    p.parent.right = null;
                }
                // 更新【p】的指针:p没有孩子,只更新父亲指针
                p.parent = null;
            }
        }
    }

10、删除后调整

10.1、删除调整分析

1)删除目标:2-3-4树叶子

在这里插入图片描述

  • 红黑树上要删除的节点,一定是 叶子节叶,或 叶子的上一层节点

    叶子的上一层节点:这类节点只有一侧叶子,如果有左右叶子,需要先替换

  • 如果是其它情况会通过替换节点,转换成上面的情况,再删除

  • 通过等价关系,可以看出被删除节点,就是2-3-4树中的叶子节点

    2-3-4树的叶子节点共有三种情况:(1节点表示空树)

    • 2节点
    • 3节点
    • 4节点

2)【删3、4节点】 直接删

2-3-4树中的3节点、4节点可以直接删,删除后不会破坏对应红黑树的平衡

2-3-4树红黑树操作
3节点:2个元素、3个分叉黑父亲 + 1个红儿子删儿子:直接删
删父亲:用儿子替,再变黑
4节点:3个元素、4个分叉黑父亲 + 2个红儿子删儿子:直接删
删父亲:用儿子替,再变黑

在这里插入图片描述

3)【删2节点】兄弟3、4节点

由于2节点没有孩子,2节点转换为红黑树后是黑色,删除黑色会打破平衡,需要先找替换节点,向下没有儿子,只能向上找父亲

自已搞不定,需要跟兄弟借,兄弟不借,找父亲借,父亲下来,兄弟找人上去替父亲当家

兄弟有得借

在这里插入图片描述

4)【删2节点】兄弟2节点

自已搞不定,兄弟也没有

在这里插入图片描述
在这里插入图片描述

10.2、fixAfterDeletion()

private

    /**
     * 删除后调整
     * 旋转、变色
     *
     * @param x
     */
    private void fixAfterDeletion(Node x) {
        /**
         * 不是根节点,并且是黑色节点,才需要调整
         * 【调整情况二:无孩子黑节点(2节点)】
         * 由于2节点没有孩子,2节点转换为红黑树后是黑色
         * 删除黑色会打破平衡,需要先找替换节点,向下没有儿子,只能向上找父亲
         */
        while (x != root && colorOf(x) == BLACK) {
            // x 是父亲左孩子
            if (x == leftOf(parentOf(x))) {
                // 找兄弟借
                // 兄弟节点:因为自已是左孩子,兄弟在右边
                Node sib = rightOf(parentOf(x));

                // 如果sib是红色,从2-3-4树中看,不是真正的兄弟节点,找真正的兄弟节点(黑色)
                if (colorOf(sib) == RED) {
                    // 自已变黑
                    setColor(sib, BLACK);
                    // 父亲变红
                    setColor(parentOf(x), RED);
                    // 以父亲为支点左旋
                    rotateLeft(parentOf(x));
                    // 此时父亲的右孩子是真正的兄弟
                    sib = rightOf(parentOf(x));
                }

                // -----两种小情况-----
                // 1、兄弟没得借:单身
                // colorOf(leftOf(sib)) == BLACK 等价于 leftOf(sib) == null
                if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) {
                    /**
                     * 自已和兄弟都是黑色单身
                     * 自已被删后,为了保持黑平衡,兄弟由黑变红
                     * 然后,x 指向父亲,依次向上查找,直到找到一个红色父亲,退出循环
                     * 最后,把红色父亲变黑,实现整棵树黑平衡(变黑操作在退出循环后,由本方法最后一句实现)
                     */
                    setColor(sib,RED);
                    x = parentOf(x);
                }
                // 2、兄弟有得借:3节点、4节点
                else {
                    // -----再分两种小情况------

                    /**
                     * 【1、兄弟是3节点:有左孩子】
                     * --------------------------
                     * 有左孩子,没有右孩子
                     * 先变色,后右旋,把左孩子情况转换成右孩子情况
                     * 转换后【3节点】操作和【4节点】一致
                     * --------------------------
                     * colorOf(rightOf(sib)) == BLACK 等价于 rightOf(sib) == null
                     */
                    if (colorOf(rightOf(sib)) == BLACK) {
                        // 兄弟的左孩子变黑
                        setColor(leftOf(sib), BLACK);

                        // 兄弟变红
                        setColor(sib, RED);

                        // 兄弟右旋
                        rotateRight(sib);

                        // 右旋后,当前节点x的父亲的右孩子成为兄弟,就是原来兄弟的左孩子
                        sib = rightOf(parentOf(x));
                    }

                    /**
                     * 【2、兄弟是4节点;或者兄弟是3节点,有右孩子】
                     * -------------------------
                     * 以当前节点 x 的父亲为支点左旋
                     * 左旋前先变色
                     * -------------------------
                     */
                    // 兄弟颜色变为父亲颜色
                    // 兄弟是要上位,替换父亲位置的
                    setColor(sib, colorOf(parentOf(x)));

                    // 父亲颜色变黑
                    // 因为要删除的目标是没有孩子的【黑节点x】,父亲要下来替换x,为了保持黑平衡,父亲要变黑
                    setColor(parentOf(x), BLACK);

                    // 兄弟右孩子变黑
                    // 因为兄弟的右孩子,是要上位,替换兄弟位置的
                    // 真正的兄弟都是黑色,为了保持黑平衡,兄弟的右孩子要变黑
                    setColor(rightOf(sib), BLACK);

                    // 以当前节点 x 的父亲为支点左旋
                    rotateLeft(parentOf(x));

                    // 本轮调整完毕,结束循环
                    x = root;
                }
            }
            // x 是父亲右孩子
            else {
                // 找兄弟借
                // 兄弟节点:因为自已是右孩子,兄弟在左边
                Node sib = leftOf(parentOf(x));

                // 如果sib是红色,从2-3-4树中看,不是真正的兄弟节点,找真正的兄弟节点(黑色)
                if (colorOf(sib) == RED) {
                    // 自已变黑
                    setColor(sib, BLACK);
                    // 父亲变红
                    setColor(parentOf(x), RED);
                    // 以父亲为支点右旋
                    rotateRight(parentOf(x));
                    // 此时父亲的左孩子是真正的兄弟
                    sib = leftOf(parentOf(x));
                }

                // -----两种小情况-----
                // 1、兄弟没得借:单身
                // colorOf(leftOf(sib)) == BLACK 等价于 leftOf(sib) == null
                if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) {
                    /**
                     * 自已和兄弟都是黑色单身
                     * 自已被删后,为了保持黑平衡,兄弟由黑变红
                     * 然后,x 指向父亲,依次向上查找,直到找到一个红色父亲,退出循环
                     * 最后,把红色父亲变黑,实现整棵树黑平衡(变黑操作在退出循环后,由本方法最后一句实现)
                     */
                    setColor(sib,RED);
                    x = parentOf(x);
                }
                // 2、兄弟有得借:3节点、4节点
                else {
                    // -----再分两种小情况------

                    /**
                     * 【1、兄弟是3节点:有右孩子】
                     * --------------------------
                     * 有右孩子,没有左孩子
                     * 先变色,后左旋,把右孩子情况转换成左孩子情况
                     * 转换后【3节点】操作和【4节点】一致
                     * --------------------------
                     * colorOf(leftOf(sib)) == BLACK 等价于 leftOf(sib) == null
                     */
                    if (colorOf(leftOf(sib)) == BLACK) {
                        // 兄弟的右孩子变黑
                        setColor(rightOf(sib), BLACK);

                        // 兄弟变红
                        setColor(sib, RED);

                        // 兄弟左旋
                        rotateLeft(sib);

                        // 左旋后,当前节点x的父亲的左孩子成为兄弟,就是原来兄弟的右孩子
                        sib = leftOf(parentOf(x));
                    }

                    /**
                     * 【2、兄弟是4节点;或者兄弟是3节点,有左孩子】
                     * -------------------------
                     * 以当前节点 x 的父亲为支点右旋
                     * 右旋前先变色
                     * -------------------------
                     */
                    // 兄弟颜色变为父亲颜色
                    // 兄弟是要上位,替换父亲位置的
                    setColor(sib, colorOf(parentOf(x)));

                    // 父亲颜色变黑
                    // 因为要删除的目标是没有孩子的【黑节点x】,父亲要下来替换x,为了保持黑平衡,父亲要变黑
                    setColor(parentOf(x), BLACK);

                    // 兄弟左孩子变黑
                    // 因为兄弟的左孩子,是要上位,替换兄弟位置的
                    // 真正的兄弟都是黑色,为了保持黑平衡,兄弟的左孩子要变黑
                    setColor(leftOf(sib), BLACK);

                    // 以当前节点 x 的父亲为支点右旋
                    rotateRight(parentOf(x));

                    // 本轮调整完毕,结束循环
                    x = root;
                }
            }
        }

        /**
         * 以下代码适用于两种情况
         * ============== 1 ================
         * -----如果 x 是红色,上面代码不执行-----
         * 【调整情况一:黑父亲 + 红独子(3节点)】
         * 这种情况 x 是红孩子,真正删除的是它的黑父亲
         * x 作为替补,替换父亲的位置
         * 需要调整的是 x
         * 为了保持黑平衡,只需要把 x 染黑即可
         * 父亲已亡,红孩子升级为黑父亲
         * ============== 2 ================
         * 两个黑兄弟,删除一个后,另一个变红
         * 为了保持黑平衡,会循环查找红父亲
         * 找到后把红父亲变黑
         */
        setColor(x, BLACK);
    }

参考:

视频:https://www.bilibili.com/video/BV1864y1S7si

源码:java.util.TreeMap (简直是艺术品)

在这里插入图片描述

源码作者:Josh Bloch and Doug Lea(道格·利)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

土味儿~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值