阅读 203

红黑树

1. 红黑树概述

红黑树,英文名 Red-Black Tree,是一个自平衡的二叉搜索树。以前也叫做平衡二叉B树(Symmetric Binary B-tree)。

红黑树必须满足以下 5 条性质:
1. 每个节点是红色或着黑色的;
2. 树的根节点始终是黑色的;
3. 每个叶子节点(外部节点, null节点)是黑色;

注意:这里的叶子节点和之前系列文章的叶子节点不同,这里是指额外添加的 null 节点。如 下图1 中的 null节点

4. 每个红色结点的子结点都是黑色;

也就是说:红色节点的父节点都是黑色;
从根节点到叶子节点的所有路径上不能有 2 个连续的红色节点.

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

下图即为一棵红黑树:

image.png

注:本文后面所有示图均不画出 null 节点。

红黑树中用到的几个常用的术语:
parent:父节点
sibling:兄弟节点
uncle:叔父节点(parent的兄弟节点)
grand:祖父节点(parent的父节点)

image.png

2. 红黑树和4阶B树

2.1 红黑树和4阶B树的对比

可以通过 4阶B树 来更好的理解红黑树。

4阶B树简介传送门

先上图,对比红黑树和 4阶B树

image.png 如上图所示: 对于红黑树,我们将黑色节点和它的红色子节点合并在一起(或者说将红色(兄弟)节点向上同它(们)的父节点合并在一起)就可以得到一个 4阶B树 节点。

无红色子节点的黑色节点单独为一个B树节点。

在对比红黑树和 4阶B树 时,在相互转化过程中,我们约定:

  • 只有一个元素的节点,节点标黑;
  • 有两个元素的,先加入该节点的标黑,后加入的标红。当转化为红黑树时,可分裂为两个节点,并将标红的节点作为标黑节点的子节点;
  • 有三个元素的,中间元素标黑,左右元素标红。当转化为红黑树时,分裂为3个节点,并将左右两个红节点作为黑节点的子节点。

因此,上图中右下角的 4阶B树,按照上述约定,可以转化为右上角的红黑树(调整展示样式即为左上角的红黑树)。同时右上角的红黑树也可以转化为 4阶B树

2.2 红黑树和4阶B树具有等价性

  • 红黑树中,黑节点和它的红色子节点融合在一起,形成一个B树节点。
  • 红黑树的黑节点个数与4阶B树的节点总数相等。

红黑树 vs 4阶B树中,红黑树中节点合并得到 4阶B树 的几种情况:
1) 黑色节点和它的两个红色子节点合并成一个 4阶B树 节点。此时即为:红黑红。
2) 黑色节点和它仅有的右红色子节点合并成一个 4阶B树 节点。此时即为:黑红。
3) 黑色节点和它仅有的左红色子节点合并成一个 4阶B树 节点。此时即为:红黑。
4) 黑色节点没有红色子节点,自己独立成一个 4阶B树 节点。此时即为:黑。

下图分别展示了四种情况: image.png

3. 红黑树的添加

基于红黑树和4阶B树的等价性,在红黑树添加元素时,可以对比 4阶B树,方便分析其处理逻辑。

我们知道,在B树中,新元素必定是添加到叶子节点中,节点元素对应红黑树中的各节点。

在红黑树中,我们将新添加的节点默认设置为红色,然后再根据不同情况处理相关节点,并最终恢复红黑树。

默认设为红色,则直接满足了上面红黑树的性质1、2、3、5(性质4不应定满足),这样能够让红黑树的性质尽快满足。即只需要执行相关操作满足在此基础上满足性质4即可。

由于新元素必定是添加到叶子节点中,所以红黑树添加元素的共有 12 种情况,如下图所示: image.png

在这 12 种情况中,其中 ⑤⑩⑪⑫ 四种情况的父节点为黑色,添加后直接满足性质4,故这四种情况直接添加即可。

剩下的 8 种情况,则不满足性质4,因为添加节点是红色,添加节点的父节点也是红色,即连续两个红色节点。

接下来一一分析各种添加场景的处理。

红黑树添加元素可以分为以下几种场景:

3.1 红黑树为空树

把插入节点作为根节点,并将节点染成黑色。

image.png

3.2 添加节点的父节点为黑色节点

对应上面 “添加的所有情况” 图中的 ⑤⑩⑪⑫。 直接添加即可。

image.png

3.3 添加节点的父节点为红色,且添加节点的叔父节点不为红色(叔父节点黑色或者不存在)时

对应上面 “添加的所有情况” 图中的 ⑥⑦⑧⑨。但这 4 种情况处理流程有所差异,接下来对这 4 种情况做相应的分析:

3.3.1 添加节点是父节点的左子节点,且父节点是祖父节点的左子节点,即对于祖父节点来说为 LL

即:添加节点的父节点为红色,且添加节点的叔父节点不为红色(叔父节点黑色或者不存在),且添加节点是父节点的左子节点,父节点是祖父节点的左子节点。

方案
1) 父节点染成黑色,祖父节点染成红色;
2) 对祖父节点进行右旋(LL - 右旋)。

示例:以下图做分析 image.png

针对上图分析:类比 4阶B树,添加 60 的时候,就 4阶B树 来讲,60、72、76 三个元素合并成一个节点,经过之前 4阶B树 和红黑树的等价性的分析,可知,60 想要添加成功并且 60、72、76 三个元素想要合并成一个接点的话,72 此时必须要变成黑色,76 要变成红色,即 红(60)黑(72)红(76) ;同时红黑树转换成 4阶B树 时:黑色节点和其红色子节点合并成B树节点,也就是说此时的黑色节点 72 需要成为 60 和 76 两个红色节点的父节点,所以当 72 变成黑色之后,76 的父节点需要指向 72,此时对于祖父节点 76 来说相当于执行了一次右旋操作。

整个过程可总结为:父节点 72 染成黑色,祖父节点 76 染成红色;然后对祖父节点进行右旋。最终得到上图下半部分的结果图。


3.3.2 添加节点是父节点的左子节点,且父节点是祖父节点的右子节点,即对于祖父节点来说为 RR:

即:添加节点的父节点为红色,且添加节点的叔父节点不为红色(叔父节点黑色或者不存在),且添加节点是父节点的左子节点,且父节点是祖父节点的右子节点。

这种情况和上面 3.3.1 的情况刚好相反,所以操作思路是一样的,只不过旋转操作相反。
方案
1) 父节点染成黑色,祖父节点染成红色;
2) 对祖父节点进行左旋(RR - 左旋)。

如下图:添加节点 52 对于祖父节点 46 来说,相当于 RR,所以将父节点 50 染成黑色,祖父节点 46 染成红色;然后对祖父节点执行左旋操作,即得到下半部分的结果图 image.png


3.3.3 添加节点是父节点的右子节点,且父节点是祖父节点的左子节点,即对于祖父节点来说为 LR:

即:添加节点的父节点为红色,且添加节点的叔父节点不为红色(叔父节点黑色或者不存在),且添加节点是父节点的右子节点,且父节点是祖父节点的左子节点。

分析:此时只需对父节点进行左旋转,即可转换为上面的 LL 型,所以:
方案
1)对父节点进行左旋转,转换成 LL 型;
2)再按照 LL 型进行后续操作。

示例图如下:

image.png

上图,添加节点 74 是父节点 72 的右节点,72 是祖父节点 76 的左节点,即:对于祖父节点 76 来说,相当于 LR,如图左上角那个视图。
先将父节点 72 进行左旋,此时 72 成为 74 的左子节点,74 成为 76 的左子节点,经过旋转,此时已转换成 LL 型:如左下角的视图。
按照上面 场景2.1. LL 型,对此时的父节点 74 染黑,对此时的祖父节点 76 染红,然后对 76 右旋转,得到最终的结果,如右下角视图。

其实还可以先着色,然后再进行旋转,同样可以恢复成红黑树 即:
i)添加节点染成黑色,祖父节点染成红色;
ii)对父节点进行左旋,再对祖父节点右旋。


3.3.4 添加节点是父节点的左子节点,且父节点是祖父节点的右子节点,即对于祖父节点来说为 RL:

即:添加节点的父节点为红色,且添加节点的叔父节点不为红色(叔父节点黑色或者不存在),且添加节点是父节点的左子节点,且父节点是祖父节点的右子节点。

分析:此时只需对父节点进行右旋转,即可转换为上面的 RR 型,类似于上面的场景3.3.3,只不过方向相反,所以其旋转执行反向操作,后再做调整。所以:

方案
1)对父节点进行右旋转,转换成 RR 型;
2)再按照 RR 型进行后续操作。

image.png

同样也可以先着色,然后再进行旋转,同样可以恢复成红黑树 即:
i)添加节点染成黑色,祖父节点染成红色;
ii)对父节点进行右旋,再对祖父节点左旋。

3.4 添加节点的父节点为红色,且添加节点的叔父节点为红色

对应上面所有情况中的 ①②③④,以情况①做分析 如下图: image.png

添加节点 10,其父节点 17 为红色,同时其叔父节点 33 也为红色。此时对比 4阶B树 而言,10、17、25、33 合并成一个B树节点,出现上溢(节点4个元素超过4阶B树单个节点元素上线3的限制),因此按照 B树 上溢的操作,节点内元素向上合并且左右节点分裂,此时选择 25 上溢(不选17是因为,25 本身就是 38 的子节点,这样会方便处理),同时其左右分裂为两个子节点,左节点10和17想要成为一个B树节点(时刻注意类比红黑树),则17着黑色才能同 10 合并成一个节点,因为红黑树合并成一个B树节点的情况是:黑节点和其红色子节点合并。同样右边 33 需要着黑色。此时我们完成了分裂的逻辑。继续分析 25 向上合并,此时可以看做38 和 55 新加一个25,这个时候我们可以把 25 当做新加的节点,这样就回到了新加节点的判断处理流程。此处 25 的父节点为红色,且 25 的叔父节点不为红色(没有叔父节点,红黑树中默认 nil 节点为黑色),同时 25 是父节点 38 的左节点,父节点 38 是祖父节点55 的左节点,这样就符合上面 场景2.1. ,所以此时将父节点 38 染成黑色,祖父节点 55 染成红色,然后对对祖父节点 55 进行右旋。最红恢复红黑树平衡。

其过程如图中的标注信息。

所以,整个过程可归纳为:
1)将父节点和叔父节点染成黑色;
2)祖父节点向上合并:把祖父节点当做新添加的节点进行处理。

步骤祖父节点向上合并的时候,执行 1)可能还没有恢复红黑树,则继续递归处理。如果一直处理到根节点,则只需将根节点染成黑色

对于②③④的处理与①的处理流程一样,只不过其中涉及的旋转可能有所不同,因此不再赘述。

4. 删除

在B树中,最后真正被删除的元素都在叶子节点中。
当我们在B树中找到要删除的元素时,都会找到其后继元素,用后继元素替换其值,然后在删除后继节点。

同添加节点一样,红黑树的删除也先分为两大类:删除的节点是红色节点和删除的节点是黑色节点。

4.1 删除的节点是红色节点

直接删除也满足红黑树的性质四,故直接删除即可

注意:直接删除后,同样需要将删除的节点染成红色

4.2 删除的节点是黑色节点

删除节点是黑色节点又可以分为三类
1)黑色节点无子节点(即叶子节点)
2)黑色节点有一个红色子节点
3)黑色节点有两个红色子节点。
其中3)黑色节点又两个红色子节点是不可能别直接删除的,因为会找到其后继节点替换值后再删除后继结点,所以这种情况不考虑。

而 2) 又如何判断黑色节点有一个红色子节点呢?
通过用以取代该节点的子节点是否为红色。
我们知道二叉搜索树中,删除度为1的节点的时候,是用被删除的节点的子节点替换该节点位置的。

4.2.1 删除仅有一个红色子节点的黑色节点

删除仅有一个红色子节点的黑色节点有两种情况:如下图
1)红色子节点为右子节点 红色节点 50 为黑色节点 46 的右节点。
2)红色子节点为左子节点 红色节点 72 为黑色节点 76 的右节点。

image.png

上面两种情况的操作基本一样,所以选择其中之一来分析。 我们选择红色子节点为右子节点的情况来分析:整个过程如下图所示: image.png

所以: 对于 删除仅有一个红色子节点的黑色节点 将替代的子节点染成 黑色 即可恢复红黑树平衡。

4.2.2 删除节点为黑色叶子节点,且其兄弟节点也为黑色

如下图的左半部分视图中的88 和右半部分视图中的 88,自己为黑色,且小弟节点76 也为黑色。

image.png

我们同样类比4阶B树进行分析,当我们删除左右两个视图中的 88 的时候,会导致B树下溢,需要向兄弟节点 “借” 元素来填补(兄弟节点元素向上合并,父节点元素向下填充)。可以发下当删除 左侧中的 88 时,其兄弟节点 76 有元素可借(对于B树而言,76 和 78 两个元素合并成一个B树节点)。而 右侧的 88,其兄弟节点 76是单个元素节点,无元素可以借。 针对兄弟节点有无元素可借,需要做不同的处理。因此 删除节点为黑色叶子节点,且其兄弟节点也为黑色 的场景需要再细分。

4.2.2.1 删除节点为黑色叶子节点,且其兄弟节点也为黑色,且兄弟节点至少有一个红色子节点

兄弟节点至少有一个红色子节点有三种情况,即红色右子节点、红色左子节点、以及两个红色子节点,如下图所示: image.png

这里画的三种情况是以红色子节点的情况为标准进行划分的。删除节点和兄弟节点位置也可以与图中的情况相反,即88 是左子节点,76 是右子节点,与图中刚好相反(影响后续的旋转方向)。

我们以红色右子节点(上图最左边的视图)为例进行分析:
同样类比B树进行:
1)删除节点 88,B树下溢;
2)向兄弟节点借元素,元素 78 向上合并,父节点 80 向下填充。此时如果以78 为参照,对于 80来说,刚好符合 LR(78是父节点76的右节点,76是祖父节点的左节点),因此对 76 执行左旋转,对 80执行右旋转,解决下溢的问题;
3)对旋转之后的中心节点染为最初删除节点的父节点的颜色,同时旋转之后的新的左右子节点然黑色(76 和 80 要想成为一个独立的 B 树节点,则必须为黑色)。

整个过程和结果如下图所示:

image.png

另外两种删除情况类似,不在赘述。

4.2.2.2 删除节点为黑色叶子节点,且其兄弟节点也为黑色,且兄弟节点没有一个红色子节点

image.png

当然如果删除节点的父节点为黑色,其处理会稍有不同:

image.png

4.2.3 删除节点为黑色叶子节点,且其兄弟节点为红色

从B树的角度来看,兄弟节点为红色,则必然无元素可借。

红黑树的黑色节点和其红色子节点合并成4阶B树。

如果直接从该红色节点的子节点借元素,则会比较麻烦。我们可以想办法将红色兄弟节点的子节点(必然是黑色)变成兄弟节点,这样就转换成了 删除节点为黑色叶子节点,且其兄弟节点也为黑色的情况。
1)将兄弟节点染成黑色,同时将父节点染成红色;
2)对父节点进行相应旋转;
3)转换为 删除节点为黑色叶子节点,且其兄弟节点也为黑色 的情况,按照对应的操作,最终恢复红黑树平衡。

示例:如下图所示 image.png 删除黑色叶子节点 88 的兄弟节点为红色,倘若我们删除 88,出现下溢,而其兄弟节点 55 无元素可借。我们将兄弟节点 55 染黑,然后将父节点 80 染红。再对父节点 80 进行右旋操作,此时已转换成上面所分析处理的 删除节点为黑色叶子节点,且其兄弟节点也为黑色 的情况(如图右下角部分视图),按照该情况的处理,最终可以是红黑树恢复平衡(如图左下角部分视图)。

5. 添加删除代码

5.1 红黑树节点

public static class RBTreeNode<E> {
    boolean color = RED; //添加节点默认红色
    E element; //节点数据
    RBTreeNode<E> left; //左节点
    RBTreeNode<E> right; //右节点
    RBTreeNode<E> parent; //父节点

    public RBTreeNode(E element, RBTreeNode<E> parent) {
        this.element = element;
        this.parent = parent;
    }

    public boolean hasTwoChildren() {
        return left != null && right != null;
    }

    /**
     * 是否是左子节点
     */
    public boolean isLeftChild() {
        return (parent != null && this == parent.left);
    }

    /**
     * 是否是右子节点
     */
    public boolean isRightChild() {
        return (parent != null && this == parent.right);
    }

    /**
     * 获取节点的兄弟节点
     * @return
     */
    public RBTreeNode<E> sibling() {
        if (isLeftChild()) {
            return parent.right;
        }

        if (isRightChild()) {
            return parent.left;
        }

        return null;
    }
}

复制代码

5.2 添加删除全部完整代码

/**
 * 红黑树
 * @param <E> 泛型
 */
public class RBTree<E> {
    // 标记节点颜色
    private static final boolean RED = false;
    private static final boolean BLACK = true;

    private int size; // 树元素大小
    private RBTreeNode<E> root; // 根节点
    private Comparator<E> comparator; // 比较器,可由外部控制比较器规则

    // 外部不传入比较器规则,则使用默认的存入节点的对象实现的比较规则
    public RBTree() {
        this(null);
    }

    // 外部传入比较器规则,则使用传入的比较规则
    public RBTree(Comparator<E> comparator) {
        this.comparator = comparator;
    }

    /**
     * 添加元素
     */
    public void add(E element) {
        elementNotNullCheck(element);

        // 添加根节点
        if (root == null) {
            root = new RBTreeNode<>(element, null);
            size++;
            return;
        }

        // 添加的不是第一个节点
        // 1.找到父节点
        RBTreeNode<E> parent = root;
        RBTreeNode<E> node = root;
        int cmp = 0;
        while (node != null) {
            // 保存父节点,如果此时不保存的话,在 while 循环发现 node == null 的时候,跳出循环
            parent = node;

            cmp = compare(element, node.element);
            if (cmp > 0) {
                node = node.right;
            } else if (cmp < 0) {
                node = node.left;
            } else { // 相等
                node.element = element;
                return;
            }
        }

        // 2.看看插入到父节点的哪个位置
        RBTreeNode<E> newNode = new RBTreeNode<>(element, parent);
        if (cmp > 0) {
            parent.right = newNode;
        } else {
            parent.left = newNode;
        }
        size++;
    }

    /**
     * 添加节点后恢复红黑树平衡
     * @param node 新添加的节点
     */
    private void fixAfterAdd(RBTreeNode<E> node) {
        RBTreeNode <E> parent = node.parent;
        // 添加的是根节点
        if (parent == null) {
            black(node); //根节点着黑色
            return;
        }

        // 插入节点的父节点是黑色,直接返回(因为插入的节点默认是红色)
        if (isBlack(parent)) {
            return;
        }

        /*  插入节点的父节点是红色  */

        // 获取节点的叔父节点(父节点的兄弟节点)
        RBTreeNode<E> uncle = parent.sibling();

        // 获取节点的祖父节点(父节点的父节点)
        RBTreeNode<E> grand = parent.parent;

        // 叔父节点是红色
        if (isRed(uncle)) {
            black(parent);
            black(uncle);

            // 把祖父节点当做是新添加的节点,继续向上恢复红黑树
            red(grand);
            fixAfterAdd(grand);
            return;
        }

        // 能来到这里,说明叔父节点不是红色
        if (parent.isLeftChild()) { // L
            if (node.isLeftChild()) { // LL
                black(parent);
                red(grand);
                rotateRight(grand);
            } else { // LR
                red(grand);
                black(node);
                rotateLeft(parent);
                rotateRight(grand);
            }

        } else { // R
            if (node.isLeftChild()) { // RL
                black(node);
                red(grand);
                rotateRight(parent);
                rotateLeft(grand);
            } else { // RR
                black(parent);
                red(grand);
                rotateLeft(grand);
            }
        }
    }

    /**
     * 删除元素
     */
    public void remove(E element) {
        // 通过元素值查找节点
        RBTreeNode<E> node = node(element);
        if (node == null) return;
        size--;
        if (node.hasTwoChildren()) { // 度为2的节点
            // 找到后继结点
            RBTreeNode<E> s = successor(node);
            // 用后继结点的值覆盖读为2的节点的值
            node.element = s.element;
            // 删除后继结点,这里只需要将node指向s,后面代码统一删除 node 即可
            node = s;
        }

        // 删除 node 节点(此时 node 节点的度必然是 0 或者 1,因为node的度未2的情况,上面已经特殊处理了)
        RBTreeNode<E> replaceNode = node.left != null ? node.left : node.right;
        if (replaceNode != null) { // node 是度为1的节点,使用子节点替代replaceNode替换node的位置
            // 更改 parent
            replaceNode.parent = node.parent;

            if (node.parent == null) { // 删除的是根节点
                root = replaceNode;
            }

            // 更改parent的left、right指向
            if (node == node.parent.left) {
                node.parent.left = replaceNode;

            } else if (node == node.parent.right) {
                node.parent.right = replaceNode;
            }

            // 删除节点之后的处理
            fixAfterRemove(replaceNode);

        } else if(node.parent == null) { // node是叶子节点且根节点
            root = null;

            // 删除节点之后的处理
            fixAfterRemove(replaceNode);
        } else { // node 是叶子节点但不是根节点
            if (node == node.parent.right) {
                node.parent.right = null;
            } else {
                node.parent.left = null;
            }

            // 删除节点之后的处理
            fixAfterRemove(node);
        }
    }

    /**
     * 删除元素后,恢复红黑树平衡
     * @param node 被删除的节点 或者 用以取代被删除节点的子节点(当被删除节点的度为1)
     */
    private void fixAfterRemove(RBTreeNode<E> node) {
        // 如果删除的节点是红色
        // 或者 用以取代删除节点的子节点是红色
        if (isRed(node)) {
            black(node);
            return;
        }

        RBTreeNode<E> parent =  node.parent;
        // 删除的是根节点
        if (parent == null) return;

        // 删除的是黑色叶子节点【下溢】
        // 判断被删除的node是左还是右
        boolean left = parent.left == null || node.isLeftChild();
        RBTreeNode<E> sibling = left ? parent.right : parent.left;
        if (left) { // 被删除的节点在左边,兄弟节点在右边
            if (isRed(sibling)) { // 兄弟节点是红色
                black(sibling);
                red(parent);
                rotateLeft(parent);
                // 更换兄弟
                sibling = parent.right;
            }

            // 兄弟节点必然是黑色
            if (isBlack(sibling.left) && isBlack(sibling.right)) {
                // 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
                boolean parentBlack = isBlack(parent);
                black(parent);
                red(sibling);
                if (parentBlack) {
                    fixAfterRemove(parent);
                }
            } else { // 兄弟节点至少有1个红色子节点,向兄弟节点借元素
                // 兄弟节点的左边是黑色,兄弟要先旋转
                if (isBlack(sibling.right)) {
                    rotateRight(sibling);
                    sibling = parent.right;
                }

                color(sibling, colorOf(parent));
                black(sibling.right);
                black(parent);
                rotateLeft(parent);
            }
        } else { // 被删除的节点在右边,兄弟节点在左边
            if (isRed(sibling)) { // 兄弟节点是红色
                black(sibling);
                red(parent);
                rotateRight(parent);
                // 更换兄弟
                sibling = parent.left;
            }

            // 兄弟节点必然是黑色
            if (isBlack(sibling.left) && isBlack(sibling.right)) {
                // 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
                boolean parentBlack = isBlack(parent);
                black(parent);
                red(sibling);
                if (parentBlack) {
                    fixAfterRemove(parent);
                }
            } else { // 兄弟节点至少有1个红色子节点,向兄弟节点借元素
                // 兄弟节点的左边是黑色,兄弟要先旋转
                if (isBlack(sibling.left)) {
                    rotateLeft(sibling);
                    sibling = parent.left;
                }

                color(sibling, colorOf(parent));
                black(sibling.left);
                black(parent);
                rotateRight(parent);
            }
        }
    }

    /**
     * 左旋转 - RR 型
     */
    private void rotateLeft(RBTreeNode<E> node) {
        RBTreeNode<E> subNode = node.right;
        RBTreeNode<E> subSubNode = subNode.left;
        node.right = subSubNode;
        subNode.left = node;

        // 让subNode成为最小失衡子树的根节点
        subNode.parent = node.parent;
        if (node.isLeftChild()) {
            node.parent.left = subNode;
        } else if (node.isRightChild()) {
            node.parent.right = subNode;
        } else {
            root = subNode;
        }

        // 更新 subSubNode 的 父节点
        if (subSubNode != null) {
            subSubNode.parent = node;
        }

        // 更新 node 的父节点
        node.parent = subNode;
    }

    /**
     * 右旋转 - LL 型
     */
    private void rotateRight(RBTreeNode<E> node) {
        RBTreeNode<E> subNode = node.left;
        RBTreeNode<E> subSubNode = subNode.right;
        node.left = subSubNode;
        subNode.right = node;

        // 让subNode成为最小失衡子树的根节点
        subNode.parent = node.parent;

        if (node.isLeftChild()) {
            node.parent.left = subNode;
        } else if (node.isRightChild()) {
            node.parent.right = subNode;
        } else {
            root = subNode;
        }

        // 更新 subSubNode 的 父节点
        if (subSubNode != null) {
            subSubNode.parent = node;
        }

        // 更新 node 的父节点
        node.parent = subNode;
    }

    /**
     * 通过值找到节点
     */
    private RBTreeNode<E> node(E element) {
        RBTreeNode<E> node = root;
        while (node != null) {
            int cmp = compare(element, node.element);

            // 相等(即找到)直接返回
            if (cmp == 0) return node;

            // 查询元素比节点值大,则继续查找节点的右子树
            if (cmp > 0) {
                node = node.right;
            } else {
                node = node.left;
            }
        }

        // while 循环结束,未查到对应节点
        return null;
    }

    /**
     * 获取一个节点的后继结点
     */
    private RBTreeNode<E> successor(RBTreeNode<E> node) {
        if (node == null) return null;

        // 右子树不为空,前驱结点在右子树中(right.left.left...)
        if (node.right != null) {
            RBTreeNode<E> p = node.right;
            while (p.left != null) {
                p = p.left;
            }
            return p;
        }

        // 右子树为空,从父节点、祖父节点..中寻找前驱结点
        while (node.parent != null && node == node.parent.right) {
            node = node.parent;
        }
        return node.parent;
    }

    /**
     * 节点着色
     * @param node 着色节点
     * @param color 需要着的颜色
     * @return 返回着好色的节点
     */
    private RBTreeNode<E> color(RBTreeNode<E> node, boolean color) {
        if (node == null) return node;
        node.color = color;
        return node;
    }

    /**
     * 节点着红色
     * @param node 着红色的节点
     * @return
     */
    private RBTreeNode<E> red(RBTreeNode<E> node) {
        return color(node, RED);
    }

    /**
     * 节点着黑色
     * @param node 着黑色的节点
     * @return
     */
    private RBTreeNode<E> black(RBTreeNode<E> node) {
        return color(node, BLACK);
    }

    /**
     * 获取节点的颜色
     */
    private boolean colorOf(RBTreeNode<E> node) {
        return node == null ? BLACK : ((RBTreeNode<E>)node).color;
    }

    /**
     * 节点是否为红色
     */
    private boolean isRed(RBTreeNode<E> node) {
        return colorOf(node) == RED;
    }

    /**
     * 节点是否为黑色
     */
    private boolean isBlack(RBTreeNode<E> node) {
        return colorOf(node) == BLACK;
    }

    /**
     * 元素空(null)检查
     * @param element 元素
     */
    private void elementNotNullCheck(E element) {
        if (element == null) {
            throw new IllegalArgumentException("element must not be null");
        }
    }

    /**
     * 节点值比较
     * @param e1
     * @param e2
     * @return 返回值等于0,代表e1 == e2;返回值大于0,代表e1 > e2;返回值小于0,代表e1 < e2;
     */
    private int compare(E e1, E e2) {
        // 外部传入了可比较器则使用传入的可比较器
        if (this.comparator != null) {
            return comparator.compare(e1, e2);
        }

        // 外部未可比较器则使用e1对应类实现的Comparable的比较器,因为二叉搜索树的节点必须存放可比较的对象元素
        return ((Comparable<E>)e1).compareTo(e2);
    }
}
复制代码

6. 红黑树的平衡和复杂度

文章开始的时候,列举了红黑树的5条性质,那么为何那5条性质,可以保证红黑树时平衡的呢?
那5条性质 可以保证红黑树和4阶B树等价 相比于AVL树,红黑树的平衡标准比较宽松:没有一条路径会大于其他路径的两倍 红黑树时一种弱平衡、黑高度平衡。 另外,红黑树的最大高度是 2 * log2(n + 1),依然是O(logn)。

红黑树的复杂度
搜索:O(logn)

添加:O(logn), O(1)次的旋转操作

删除:O(logn), O(1)次的旋转操作



AVL 树和红黑树的对比

  • AVL 树

1) 平衡标注比较严格:每个左右自述的高度差不超过1;

2) 最大高度是 1.44 ∗ log2 n + 2 − 1.328(100W个节点,AVL树最大树高28);

3) 搜索、添加、删除都是 O(logn) 复杂度,其中添加仅需 O(1) 次旋转调整、删除最多需要 O(logn) 次旋转调整。

  • 红黑树

1) 平衡标准比较宽松:没有一条路径会大于其他路径的2倍;

  1. 最大高度是 2 ∗ log2(n + 1)( 100W个节点,红黑树最大树高40);

3) 搜索、添加、删除都是 O(logn) 复杂度,其中添加、删除都仅需 O(1) 次旋转调整。


AVL 树和红黑树的选择

  • 搜索的次数远远大于插入和删除,选择AVL树;搜索、插入、删除次数几乎差不多,选择红黑树;
  • 相对于AVL树来说,红黑树牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树;
  • 红黑树的平均统计性能优于AVL树,实际应用中更多选择使用红黑树。
文章分类
代码人生
文章标签