1 Star 0 Fork 165

shk / Java-Review

forked from icanci / Java-Review 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
Java-集合框架-HashMap底层-红黑树深度解读.md 28.39 KB
一键复制 编辑 原始数据 按行查看 历史

Java 集合框架 - HashMap 底层 红黑树深度解读

1.概述

**数据结构的基石,我认为只有2个:数组和链表。所有的数据结构都可以通过二者演变出来 **

2.红黑树原理讲解

 ├─ 红黑树的性质
 ├─ 红黑树有几种变化策略?(为满足红黑树性质)
 │    ├─ 改变颜色
 │    ├─ 左旋
 │    ├─ 右旋
 ├─ 红黑树的查找
 ├─ 红黑树的插入
 │    ├─ 情景1:红黑树为空树
 │    ├─ 情景2:插入节点的Key已经存在
 │    ├─ 情景3:插入节点的父节点为黑色
 │    ├─ 情景4:插入节点的父节点为红色
 │    │    ├─ 情景4.1:叔叔节点存在,并且为红色(父 - 叔 双红)
 │    │    ├─ 情景4.2:叔叔节点不存在,或者为黑色,父节点为爷爷节点的左子树
 │    │    │    ├─ 情景4.2.1:插入节点为其父节点的左子节点(LL情况)
 │    │    │    ├─ 情景4.2.2:插入节点为其父节点的右子节点(LR情况)
 │    │    ├─ 情景4.3:叔叔节点不存在,或者为黑色,父节点为爷爷节点的右子树
 │    │    │    ├─ 情景4.3.1:插入节点为其父节点的右子节点(RR情况)
 │    │    │    ├─ 情景4.3.2:插入节点为其父节点的左子节点(RL情况)
 ├─ 红黑树的插入案例分析
2.1 红黑树的性质
  • 性质1:每个节点要么是黑色,要么是红色
  • 性质2:根节点是黑色
  • 性质3:每个叶子节点(NIL)是黑色
  • 性质4:每个红色节点的两个子节点一定都是黑色
  • 性质5:任意一节点到每个叶子节点的路径都包含数量相同黑节点,俗称黑高
    • 从性质5可以推出:
    • 性质5.1:如果一个节点存在黑子节点,那么该节点肯定有两个子节点

红黑树并不是一个完美平衡二叉树,从图上可以看到,根节点P的左子树显然比右子树高

但是左子树和右子树的黑节点的层数是相等的,也即任意一个节点到每个叶子节点的路径都包含数量相同的黑节点(性质5)

所以我们叫红黑树的这种平衡为黑色完美平衡

以上就是红黑树的性质,只有这棵树满足以上性质,这棵树就是趋近平衡状态的

2.2 红黑树有几种变化策略?(为满足红黑树性质)

前面讲到的红黑树可以自平衡,它靠的是什么?三种操作:左旋、右旋和变色

  • 变色:节点的颜色由红变黑或由黑变红
  • 左旋:以某个节点作为支点(旋转节点),其右子节点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的右子节点,左子节点保持不变。
  • 右旋:以某个节点作为支点(旋转节点),其左子节点变为旋转节点的父节点,左子节点的右子节点变为旋转节点的左子节点,右子节点保持不变。

左旋展示:

右旋展示:

2.3 红黑树查找

2.4 红黑树的插入

插入操作:

  • 查找插入的位置
  • 插入后自平衡

注意:插入节点,必须为红色,理由很简单,红色在父节点(如果存在)为黑色节点的时候,红黑树的黑色平衡没有被破坏,不需要做自平衡操作;如果父节点为红色节点,这个时候就要重新平衡。但是如果插入的节点是黑色,那么插入位置的子树节点总是多1,必须做自平衡。

在开始每个情景的讲解前,先约定一下名称 父亲节点 - 叔叔节点 - 爷爷节点

红黑树插入节点情景分析
2.4.1 情景1:红黑树为空树

最简单的一种场景,直接把插入节点作为根节点就行

注意:根据红黑树性质2:根节点是黑色,还需要把插入结点设为黑色

2.4.2 情景2:插入节点的Key已经存在

处理:更新当前节点的值,为插入节点的值

2.4.3 情景2:插入节点的父节点为黑节点

由于插入的节点是黑色的,当插入的节点为黑色的时候,并不会影响红黑树的平衡,直接插入即可,无需做自平衡

2.4.4 插入的节点的父节点为红色

再次回想一下红黑树的性质2:根节点是黑色。如果插入节点的父节点是红节点,那么该父节点不可能为根节点,所以插入节点总是存在祖父节点,这一点狠关键,因为后续的旋转操作肯定需要祖父节点的参与。

2.4.4.1 叔叔节点存在并且为红节点

依据红黑树的性质4可知:红色节点不能相连 = > 祖父节点肯定为黑节点

因为不可能同时存在两个相连的红节点。那么此时该插入了子树的红黑层数的情况是:黑红红。显然最简单的处理方式是:红黑红

处理:

  • 将P和U改为黑色
  • 将PP改为红色
  • 将PP设置为当前节点,进行后续处理

可以看到,我们把PP节点设为红色了,如果PP的父节点是黑色,那么无需在做任何处理

但是如果PP的父节点是红色,就违反红黑树性质了,所以需要将PP设置为当前节点,继续做插入操作自平衡处理,直到平衡为止。

2.4.4.2 叔叔节点不存在或者为黑节点,并且插入节点的父亲节点是祖父节点的左子节点

注意:单纯从插入来看,叔叔节点非红即空(NIL节点),否则的话破坏了红黑树性质5,此路径会比其他路径多一个黑色节点

2.4.4.2.1新插入节点,为其父节点的左子节点(LL红色情况)

处理:

  • 变颜色:将P设置为黑色,将PP设置为红色
  • 对PP节点进行右旋

2.4.4.2.2 新插入节点,为其父节点的右子节点(LR红色情况)

处理:

  • 对P进行左旋
  • 将P设置为当前节点,得到LL的红色情况
  • 按照LL红色情况处理
    • 变颜色,将P设置为黑色,将PP设置为红色
    • 右旋PP节点

2.4.4.3 叔叔节点不存在,或者为黑色,父节点为爷爷节点的右子树

其实就是 2.4.4.2 的反方向

2.4.4.3.1 插入节点为其父节点的右子节点(RR情况)

处理:

  • 变颜色,将P设置为黑色,将PP设置为红色
  • 对PP节点进行左旋

2.4.4.3.2 插入节点为其父节点的左子节点(RL情况)

处理:

  • 对P进行右旋
  • 将P设置为当前节点,得到RR的情况
  • 按照RR的处理

2.5 红黑树的插入案例分析

演示的网站:

数据结构演示网站1:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

数据结构演示网站2:https://visualgo.net/zh

数据结构演示网站3:https://algorithm-visualizer.org/

网站1截图:

1595917256125

网站2截图:

1595917294390

网站3截图:

1595917388048

3.手写红黑树

3.1 创建RBTree,定义颜色
3.2 创建RBNode
3.3 辅助方法定义:parentOf(node)、isRed(node)、isBlack(node)、setRed(node)、setBlack(node)、inOrderPrint()
3.4 左旋方法定义:leftRotate(node)
3.5 右旋方法定义:rightRotate(node)
3.6 公开插入接口方法定义:insert(K key,V value)
3.7 内部插入接口方法定义:insert(RBNode node)
3.8 修正插入导致红黑树失衡的方法定义:insertFlxUp(RBNode node)
3.9 测试红黑树的正确性
3.9.1 红黑树代码 [仅仅支持Key为字母]
// RBTree.java
package cn.icanci.tree;

/**
 * @Author: icanci
 * @ProjectName: list-set-map
 * @PackageName: cn.icanci.tree
 * @Date: Created in 2020/7/28 14:48
 * @ClassAction: RBTree
 * 
 * 3.1 创建RBTree,定义颜色
 * 3.2 创建RBNode
 * 3.3 辅助方法定义:parentOf(node)、isRed(node)、isBlack(node)、setRed(node)、setBlack(node)、inOrderPrint()
 * 3.4 左旋方法定义:leftRotate(node)
 * 3.5 右旋方法定义:rightRotate(node)
 * 3.6 公开插入接口方法定义:insert(K key,V value)
 * 3.7 内部插入接口方法定义:insert(RBNode node)
 * 3.8 修正插入导致红黑树失衡的方法定义:insertFlxUp(RBNode node)
 * 3.9 测试红黑树的正确性
 */

/**
 * 创建 创建RBTree,定义颜色
 *
 * @param <K> Key
 * @param <V> Value
 */
public class RBTree<K extends Comparable<K>, V> {
    /**
     * RED 红色节点
     */
    private static final boolean RED = true;
    /**
     * BLACK 黑色节点
     */
    private static final boolean BLACK = false;

    /**
     * 树根的引用
     */
    private RBNode root;

    public RBNode getRoot() {
        return root;
    }

    public void setRoot(RBNode root) {
        this.root = root;
    }

    /**
     * 获取当前节点的父节点
     *
     * @param node 当前节点
     * @return 返回当前节点的父节点,没有就返回null
     */
    private RBNode parentOf(RBNode node) {
        if (node != null) {
            return node.parent;
        }
        return null;
    }

    /**
     * 节点是否为红色
     *
     * @param node 当前节点
     * @return 返回当前节点的颜色,节点为null就返回false
     */
    private boolean isRed(RBNode node) {
        if (node != null) {
            return node.color == RED;
        }
        return false;
    }

    /**
     * 节点是否为黑色
     *
     * @param node 当前节点
     * @return 返回当前节点的颜色,节点为null就返回false
     */
    private boolean isBlack(RBNode node) {
        if (node != null) {
            return node.color == BLACK;
        }
        return false;
    }

    /**
     * 设置节点为红色
     *
     * @param node 当前的节点
     */
    private void setRed(RBNode node) {
        if (node != null) {
            node.color = RED;
        }
    }


    /**
     * 设置节点为黑色
     *
     * @param node 当前的节点
     */
    private void setBlack(RBNode node) {
        if (node != null) {
            node.color = BLACK;
        }
    }

    /**
     * 红黑树的中序打印
     */
    public void inOrderPrint() {
        inOrderPrint(this.root);
    }

    /**
     * 红黑树的中序打印
     *
     * @param node 根节点
     */
    private void inOrderPrint(RBNode node) {
        if (node != null) {
            inOrderPrint(node.left);
            System.out.println("Key:" + node.key + " Value:" + node.value);
            inOrderPrint(node.right);
        }
    }

    /**
     * 左旋方法
     * 左旋示意图:左旋x节点
     *    p                   p
     *    |                   |
     *    x                   y
     *   / \         ---->   / \
     *  lx  y               x   ry
     *     / \             / \
     *    ly  ry          lx  ly
     */

    /**
     * 左旋做了几件事?
     * 1.并将x的右子节点指向y的左子节点(ly) 将y的左子节点的父节点更新为x
     * 2.当x的父节点不为空的时候,更新y的父节点为x的父节点,并将x的父节点指定子树(当前x的子树位置)指定为y
     * 3.将x的父节点更新为y,将y的左子节点指向x
     */

    private void leftRotate(RBNode x) {
        // 先拿到当前节点的右子节点
        RBNode y = x.right;
        // 将x的右子节点指向y的左子节点(ly)
        x.right = y.left;
        if (y.left != null) {
            // 将y的左子节点的父节点更新为x
            y.left.parent = x;
        }
        // 当x的父节点不为空的时候,更新y的父节点为x的父节点
        // 并将x的父节点指定子树(当前x的子树位置)指定为y
        if (x.parent != null) {
            y.parent = x.parent;
            if (x == x.parent.left) {
                x.parent.left = y;
            } else {
                x.parent.right = y;
            }
        } else {
            // 说明x为根节点
            this.root = y;
            this.root.parent = null;
        }
        // 将x的父节点更新为y,将y的左子节点指向x
        x.parent = y;
        y.left = x;
    }


    /**
     * 右旋方法
     * 右旋示意图:右旋y节点
     *
     *    p                       p
     *    |                       |
     *    y                       x
     *   / \          ---->      / \
     *  x   ry                  lx  y
     * / \                         / \
     *lx  ly                      ly  ry
     */

    /**
     * 1.将y的左子节点指向x的有子节点,并且更新x的有子节点的父节点为y
     * 2.当y的父节点不为空的时候,更新x的父节点为y的父节点,更新y的父节点的指定子节点(y当前的位置)为x
     * 3.更新y的父节点为x。更新x的右子节点为y
     */

    private void rightRotate(RBNode y) {
        RBNode x = y.left;
        // 将y的左子节点指向x的有子节点,并且更新x的有子节点的父节点为y
        y.left = x.right;
        if (x.right != null) {
            x.right.parent = y;
        }
        if (y.parent != null) {
            x.parent = y.parent;
            if (y == y.parent.left) {
                y.parent.left = x;
            } else {
                y.parent.right = x;
            }
        } else {
            this.root = x;
            this.root.parent = null;
        }
        // 更新y的父节点为x。更新x的右子节点为y
        y.parent = x;
        x.right = y;
    }

    /**
     * 公开的插入方法
     *
     * @param key
     * @param value
     */
    public void insert(K key, V value) {
        RBNode node = new RBNode();
        node.setKey(key);
        node.setValue(value);
        // 新节点一定是红色
        node.setColor(RED);
        this.insert(node);
    }

    /**
     * 私有的插入方法
     *
     * @param node 需要插入的节点
     */
    private void insert(RBNode node) {
        //第一步:查找当前的node的父节点
        RBNode parent = null;
        RBNode x = this.root;
        while (x != null) {
            parent = x;
            // cmp > 0 说明node.key 大于 x.key 需要到x的右子树去寻找
            // cmp == 0 说明node.key 等于 x.key 说明需要替换操作
            // cmp < 0 说明node.key 小于 x.key 说明需要到x的左子树去寻找
            int cmp = node.key.compareTo(x.key);
            if (cmp > 0) {
                x = x.right;
            } else if (cmp < 0) {
                x = x.left;
            } else {
                x.value = node.value;
                return;
            }
        }
        node.parent = parent;
        if (parent != null) {
            // 判断node与parent的key谁大
            int cmp = node.key.compareTo(parent.key);
            // 当前node的key比parent的key大,那么就要把node放入parent的右子节点
            if (cmp > 0) {
                parent.right = node;
            } else {
                // 当前node的key比parent的key小,那么就要把node放入parent的左子节点
                parent.left = node;
            }
        } else {
            this.root = node;
        }
        // 需要调用修复红黑树平衡的方法
        this.insertFlxUp(node);
    }

    /**
     * 插入后修复红黑树平衡的方法
     *     |---情景1:红黑树为空树,将根节点染色为黑色
     *     |---情景2:插入节点的key已经存在,不需要处理
     *     |---情景3:插入节点的父节点为黑色,因为你所插入的路径,黑色节点,没有变化,红黑树依旧平衡,不需要处理
     *     情景4 需要咱们去处理
     *     |---情景4:插入节点的父节点为红色
     *          |---情景4.1:叔叔节点存在,并且为红色(父-叔 双红),将父亲节点和叔叔节点染色为黑色
     *                      并且再以爷爷节点为当前节点,进行下一轮处理
     *          |---情景4.2:叔叔节点不存在,或者为黑色,父节点为爷爷节点的左子树
     *               |---情景4.2.1:插入节点为其父节点的左子节点(LL情况)
     *                              将父亲染色为黑色,将爷爷染色为红色,
     *                              然后以爷爷点右旋,就完成了
     *               |---情景4.2.2:插入节点为其父节点的右子节点(LR情况)
     *                              以父亲节点进行一次左旋,得到LL(4.2.1) 的场景
     *                              然后指定父亲节点为当前节点,进行下一个处理
     *          |---情景4.3:叔叔节点不存在,或者为黑色,父节点为爷爷节点的右子树
     *               |---情景4.3.1:插入节点为其父节点的右子节点(RR情况)
     *                              父亲染色为黑色,将爷爷染色为红色
     *                              然后以爷爷节点左旋,就完成了
     *               |---情景4.3.2:插入节点为其父节点的左子节点(RL情况)
     *                              以父亲节点进行一次右旋,得到RR双红(4.3.1)的场景
     *                              然后指定父亲节点进行下一论处理
     */


    /**
     * 修复红黑树的方法
     *
     * @param node
     */
    private void insertFlxUp(RBNode node) {
        this.root.setColor(BLACK);
        RBNode parent = parentOf(node);
        RBNode gParent = parentOf(parent);
        // 情景4:插入节点的父节点为红色
        if (parent != null && isRed(parent)) {
            // 如果父节点是红色,那么一定存在爷爷节点,因为根节点不可能是红色
            RBNode uncle = null;
            // 父节点为爷爷节点的右左子树
            if (parent == gParent.left) {
                uncle = gParent.right;
                // 情景4.1:叔叔节点存在,并且为红色(父-叔 双红)
                if (uncle != null && isRed(uncle)) {
                    // 将父亲节点和叔叔节点染色为黑色
                    setBlack(parent);
                    setBlack(uncle);
                    setRed(gParent);
                    // 并且再以爷爷节点为当前节点,进行下一轮处理
                    insertFlxUp(gParent);
                    return;
                }
                // 情景4.2:叔叔节点不存在,或者为黑色,父节点为爷爷节点的左子树
                if (uncle == null || isBlack(uncle)) {
                    // 情景4.2.1:插入节点为其父节点的左子节点(LL情况)
                    // 将父亲染色为黑色,将爷爷染色为红色,
                    // 然后以爷爷点右旋,就完成了
                    if (node == parent.left) {
                        setBlack(parent);
                        setBlack(gParent);
                        rightRotate(gParent);
                        return;
                    }
                    // 情景4.2.2:插入节点为其父节点的右子节点(LR情况)
                    // 以父亲节点进行一次左旋,得到LL(4.2.1) 的场景
                    // 然后指定父亲节点为当前节点,进行下一个处理
                    if (node == parent.right) {
                        leftRotate(parent);
                        insertFlxUp(parent);
                        return;
                    }
                }
            } else {
                // 父节点为爷爷节点的右子树
                uncle = gParent.left;
                // 情景4.1:叔叔节点存在,并且为红色(父-叔 双红)
                if (uncle != null && isRed(uncle)) {
                    // 将父亲节点和叔叔节点染色为黑色
                    setBlack(parent);
                    setBlack(uncle);
                    setRed(gParent);
                    // 并且再以爷爷节点为当前节点,进行下一轮处理
                    insertFlxUp(gParent);
                    return;
                }
                // 情景4.3:叔叔节点不存在,或者为黑色,父节点为爷爷节点的右子树
                if (uncle == null || isBlack(uncle)) {
                    // 4.3.1:插入节点为其父节点的右子节点(RR情况)
                    // 父亲染色为黑色,将爷爷染色为红色
                    // 然后以爷爷节点左旋,就完成了
                    if (node == parent.right) {
                        setBlack(parent);
                        setRed(gParent);
                        leftRotate(gParent);
                        return;
                    }
                    // 情景4.3.2:插入节点为其父节点的左子节点(RL情况)
                    // 以父亲节点进行一次右旋,得到RR双红(4.3.1)的场景
                    // 然后指定父亲节点进行下一论处理
                    if (node == parent.left) {
                        rightRotate(parent);
                        insertFlxUp(parent);
                        return;
                    }
                }
            }
        }
    }

    /**
     * 创建 内部类 RBNode
     *
     * @param <K> Key
     * @param <V> Value
     */
    static class RBNode<K extends Comparable<K>, V> {
        // parent 父节点
        private RBNode parent;
        // left 左节点
        private RBNode left;
        // right 右节点
        private RBNode right;
        // 节点的颜色
        private boolean color;
        // key
        private K key;
        // value
        private V value;

        public RBNode() {
        }

        public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, K key, V value) {
            this.parent = parent;
            this.left = left;
            this.right = right;
            this.color = color;
            this.key = key;
            this.value = value;
        }

        public RBNode getParent() {
            return parent;
        }

        public void setParent(RBNode parent) {
            this.parent = parent;
        }

        public RBNode getLeft() {
            return left;
        }

        public void setLeft(RBNode left) {
            this.left = left;
        }

        public RBNode getRight() {
            return right;
        }

        public void setRight(RBNode right) {
            this.right = right;
        }

        public boolean isColor() {
            return color;
        }

        public void setColor(boolean color) {
            this.color = color;
        }

        public K getKey() {
            return key;
        }

        public void setKey(K key) {
            this.key = key;
        }

        public V getValue() {
            return value;
        }

        public void setValue(V value) {
            this.value = value;
        }
    }
}
3.9.2 辅助工具类
// TreeOperation.java 用来打印方法
package cn.icanci.tree;

/**
 * @Author: icanci
 * @ProjectName: list-set-map
 * @PackageName: cn.icanci.tree
 * @Date: Created in 2020/7/28 17:13
 * @ClassAction: 打印红黑树
 */
public class TreeOperation {
  /*
    树的结构示例:
              1
            /   \
          2       3
         / \     / \
        4   5   6   7
    */

    // 用于获得树的层数
    public static int getTreeDepth(RBTree.RBNode root) {
        return root == null ? 0 : (1 + Math.max(getTreeDepth(root.getLeft()), getTreeDepth(root.getRight())));
    }


    private static void writeArray(RBTree.RBNode currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {
        // 保证输入的树不为空
        if (currNode == null) {
            return;
        }
        // 先将当前节点保存到二维数组中
        res[rowIndex][columnIndex] = String.valueOf(currNode.getKey() + "-" + (currNode.isColor() ? "R" : "B") + "");

        // 计算当前位于树的第几层
        int currLevel = ((rowIndex + 1) / 2);
        // 若到了最后一层,则返回
        if (currLevel == treeDepth) {
            return;
        }
        // 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
        int gap = treeDepth - currLevel - 1;

        // 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
        if (currNode.getLeft() != null) {
            res[rowIndex + 1][columnIndex - gap] = "/";
            writeArray(currNode.getLeft(), rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
        }

        // 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
        if (currNode.getRight() != null) {
            res[rowIndex + 1][columnIndex + gap] = "\\";
            writeArray(currNode.getRight(), rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
        }
    }


    public static void show(RBTree.RBNode root) {
        if (root == null) {
            System.out.println("EMPTY!");
        }
        // 得到树的深度
        int treeDepth = getTreeDepth(root);

        // 最后一行的宽度为2的(n - 1)次方乘3,再加1
        // 作为整个二维数组的宽度
        int arrayHeight = treeDepth * 2 - 1;
        int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
        // 用一个字符串数组来存储每个位置应显示的元素
        String[][] res = new String[arrayHeight][arrayWidth];
        // 对数组进行初始化,默认为一个空格
        for (int i = 0; i < arrayHeight; i++) {
            for (int j = 0; j < arrayWidth; j++) {
                res[i][j] = " ";
            }
        }

        // 从根节点开始,递归处理整个树
        // res[0][(arrayWidth + 1)/ 2] = (char)(root.val + '0');
        writeArray(root, 0, arrayWidth / 2, res, treeDepth);

        // 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可
        for (String[] line : res) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < line.length; i++) {
                sb.append(line[i]);
                if (line[i].length() > 1 && i <= line.length - 1) {
                    i += line[i].length() > 4 ? 2 : line[i].length() - 1;
                }
            }
            System.out.println(sb.toString());
        }
    }
}
3.9.3 测试类
// RBTree.java 测试类
package cn.icanci.tree;

import java.util.Scanner;

/**
 * @Author: icanci
 * @ProjectName: list-set-map
 * @PackageName: cn.icanci.tree
 * @Date: Created in 2020/7/28 16:32
 * @ClassAction: 手写 RBTree 测试类
 */
public class RBTreeTest {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        RBTree<String, Object> rbt = new RBTree();
        while (true) {
            System.out.print("请输入Key>");
            String key = sc.next();
            System.out.println();
            rbt.insert(key, null);
            TreeOperation.show(rbt.getRoot());
        }
    }
}

4.总结与彩蛋

  • 最重要的一点就是理解红黑树的核心性质
  • 性质1:每个节点要么是黑色,要么是红色
  • 性质2:根节点是黑色
  • 性质3:每个叶子节点(NIL)是黑色
  • 性质4:每个红色节点的两个子节点一定都是黑色
  • 性质5:任意一节点到每个叶子节点的路径都包含数量相同黑节点,俗称黑高
    • 从性质5可以推出:
    • 性质5.1:如果一个节点存在黑子节点,那么该节点肯定有两个子节点
  • 对于代码实现,可以使用

画图工具 Draw.io

使用网站:https://app.diagrams.net/ 可以直接使用网站去做

此画图工具开源地址: https://github.com/jgraph/drawio

Windows版本:同级目录下 软件包/draw.io-13.0.3-windows-no-installer.exe

本地的演示图片地址:同级目录下draw.io/树从链表的演变.png

参考资料

红黑树(五)之 Java的实现

美团技术团队:红黑树深入剖析及Java实现

**下一篇:MySQL索引实现之B树和B+树 **

  • 敬请期待...
1
https://gitee.com/shokaku/Java-Review.git
git@gitee.com:shokaku/Java-Review.git
shokaku
Java-Review
Java-Review
master

搜索帮助