用Java实现红黑树

153 阅读2分钟

这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战

红黑树是什么

红黑树是一种树形数据结构,它的节点值的大小是按照左节点<父节点<右节点 这种顺序排列的。所有在搜索红黑树的值时,其复杂度是Olg(n)。如果有一组数据1,2,3,4,5,6,7按照这种顺序插入到树中,会导致所有的数据都在右节点上,这时候去这个树中搜索数据时,其复杂度依然是n,并没有任何优化,这是因为这种树结构左右失衡造成的。因此就有了平衡树这种数据结构,当发现左右节点深度不平衡时,通过一些方式使其达到平衡。但是深度高度一致的平衡树在插入数据时,会频繁的引起树的修复工作,虽然优化了查询速度,但在树的生成中会引入更多的操作,造成其复杂度的上升。红黑树对于树的高度不要求强一致,这样使其查询和插入的速度都可以。

这里写图片描述

红黑树的性质

性质1. 节点是红色或黑色。

性质2. 根节点是黑色。

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

性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

红黑树的构建

插入

对于插入我们可以分情况讨论,但对于红黑树插入其实分为两步,一步插入,一步插入修复,插入修复就是使其插入新的节点要维持5条性质

删除

删除主要分两个环节,找到要删除的节点并且删除。删除后恢复红黑树的性质。

修复

修复树的结构主要包括旋转和重新染色两种方式。

这里着重对旋转进行描述,一共分为左旋和右旋两种。

左旋
 对红黑树的节点(x)进行左旋转
     *
     * 左旋示意图(对节点x进行左旋):
     *      px                              px
     *     /                               /
     *    x                               y                
     *   /  \      --(左旋)-.           / \                #
     *  lx   y                          x  ry     
     *     /   \                       /  \
     *    ly   ry                     lx  ly  

上面对x节点进行了左旋,就是将x往左下拉一下。

大致分为4步

1.获取x(被旋转的节点)的右孩子

2.将y的左孩子设置为x的右孩子(设置x的left和ly的parent)

3.修改y的父亲节点(设置y.parent和px.left或者px.right)

4.将x的parent设置为y(y的左儿子设置为x)

private void leftRotate(RBTNode<T> x) {
        
        RBTNode<T> y = x.right;

        //将 “y的左孩子” 设为 “x的右孩子”;
        // 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”
        x.right = y.left;
        if (y.left != null)
            y.left.parent = x;

        // 将 “x的父亲” 设为 “y的父亲”
        y.parent = x.parent;

        if (x.parent == null) {
            this.mRoot = y;            // 如果 “x的父亲” 是空节点,则将y设为根节点
        } else {
            if (x.parent.left == x)
                x.parent.left = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
            else
                x.parent.right = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
        }
        
        // 将 “x” 设为 “y的左孩子”
        y.left = x;
        // 将 “x的父节点” 设为 “y”
        x.parent = y;
    }

右旋

     * 对红黑树的节点(y)进行右旋转
     *
     * 右旋示意图(对节点y进行左旋):
     *            py                               py
     *           /                                /
     *          y                                x                  
     *         /  \      --(右旋)-.            /  \                     #
     *        x   ry                           lx   y  
     *       / \                                   / \                   #
     *      lx  rx                                rx  ry
 private void rightRotate(RBTNode<T> y) {
        RBTNode<T> x = y.left;

        // 将 “x的右孩子” 设为 “y的左孩子”;
        // 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”
        y.left = x.right;
        if (x.right != null)
            x.right.parent = y;

        // 将 “y的父亲” 设为 “x的父亲”
        x.parent = y.parent;

        if (y.parent == null) {
            this.mRoot = x;            // 如果 “y的父亲” 是空节点,则将x设为根节点
        } else {
            if (y == y.parent.right)
                y.parent.right = x;    // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”
            else
                y.parent.left = x;    // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”
        }

        // 将 “y” 设为 “x的右孩子”
        x.right = y;

        // 将 “y的父节点” 设为 “x”
        y.parent = x;
    }

该文章只是简单的分析红黑树到底是什么,树的构建的详情可以在代码中可以看到。想真正的理解红黑树插入和删除的修复环节的理由,推荐去看算法导论,好好推理。

这里是我写的红黑树java版代码 github.com/yangzhenkun…