关于java:数据结构之红黑树

48次阅读

共计 4545 个字符,预计需要花费 12 分钟才能阅读完成。

红黑树

什么是红黑树

红黑树是一种常见的自均衡二分搜寻树,罕用于关联数组、字典,在各种语言的底层实现中被广泛应用。

满足红黑树的五个性质

  1. 每个节点是红色或者是彩色
  2. 根节点是彩色
  3. 每一个叶子节点(最初的空节点)是彩色
  4. 如果一个节点是红色的,那么它的孩子节点都是彩色的
  5. 从任意一个节点到叶子节点,通过的彩色节点是一样的

2- 3 树的定义

红黑树和 2 - 3 树有相似之处,咱们先理解一下什么是 2 - 3 树

满足二分搜寻树的根本性质,然而2- 3 树并不是二叉树

2- 3 树有两个节点,一个能够寄存 1 个元素,一个能够寄存 2 个元素

满足 2 - 3 树的三个性质

  1. 满足二叉搜寻树的性质
  2. 节点能够寄存一个或两个元素
  3. 每个节点有两个或三个子节点

每个节点都有 2 个子节点或者 3 个子节点,所以就叫做2- 3 树

右边节点都比 42 小,而左边节点都比 42 大,所以是满足二分搜寻树的性质。留神,13 和 33 这个节点两头是 18,是比 17 大同时比 33 小。

2- 3 树的相对平衡性

对于 2 - 3 树来说任意一个节点,左右子树的高度肯定是相等的,而相似像 AVL 这种均衡二叉树左右子树的高度相差不超过 1。

而 2 - 3 树是如何维持相对平衡性,就要从它的增加操作说起。

对于一颗空树咱们增加一个节点 42,他既是根节点也是叶子节点,这个时候如果咱们想再增加一个节点 37 应该增加到那里呢?

按均衡二叉树构造的逻辑来讲 37 比 42 小,所以增加到 42 的左孩子的地位。

然而在 2 - 3 树的中并不是这样的,

在 2 - 3 中咱们仍然是从根节点登程,请留神在 2 - 3 树中新的节点永远不会增加进空的地位

发现 42 节点的左子树为空,2- 3 树就会找到最初一个叶子节点上也就是 42 的地位,这时候 37 就会和 42 进行交融,从一个 2 节点变为了 3 节点。这个时候还是一颗相对均衡的二叉树。

如果这个时候咱们再增加一个 12 节点的话,因为 12 节点比 37 小,所以去找 37 的左孩子,然而咱们曾经晓得,37 的左孩子为空,而 2 - 3 并不会将新的节点增加到空的地位中去,这个时候咱们就会变成 4 节点

然而在 2 - 3 中最多反对 3 个节点所以这个时候就会进行决裂,同时放弃相对均衡。

如果这个时候咱们再增加一个 18 节点,咱们能够很分明的晓得 18 会找到 12 节点然而 12 节点的子节点都为空,所以 18 就会和 12 节点进行交融。

如果这个时候咱们再增加一个 6 节点,大家很分明的晓得是会和 12 节点、18 节点进行交融,这个时候变成了 4 节点,所以要进行决裂成为下图这样。

然而!这样咱们就不是相对均衡的二叉树了!

这个时候就须要咱们 12 节点去找他的父节点,发现 12 的父节点 37 是一个 2 节点,这个时候就简略了,咱们只须要让 12 和 37 节点进行交融为 3 节点,而后让 6 变成 12 和 37 的左孩子,让 18 节点变成两头孩子节点即可!

这个时候咱们顺次退出 5 和 11 节点,就会变成下图这样。

因为不满足 2 - 3 树的性质,所以咱们还是将他们进行决裂。

然而这个时候又不是相对均衡了,所以咱们须要让 6 去找父亲节点,然而父亲节点曾经是 3 节点了,不过没关系,咱们照样把他交融进去成下图这样。

这个时候咱们须要进行决裂,咱们须要把 5 和 11 别离放在 6 的左孩子和右孩子的地位,37 也同理,造成下图这样。仍然放弃着相对平衡性。

其实咱们能够分为两种状况去了解,一种就是以后节点为 3 节点而父节点为 2 节点。

另一种就是以后节点为 3 节点,父节点也为 3 节点的状况。

红黑树和 2 - 3 树的等价性

在 2 - 3 树种,咱们能够示意 2 个节点和 3 个节点。

在红黑树中,咱们能够这样示意,而后用红色的线来示意 bc 是交融的,同时 b 是比 c 小的。因为红色节点是由 3 节点拆分而来,因而所有的红色节点都只会呈现在左子树上。

在下图中只有 6 和 17 和 66 是红色节点,咱们很容易得悉,因为他们都是 3 节点。

如果形象的话能够红色节点上移一下,咱们能够看到其实是和 2 - 3 树是一样的。

放弃根节点为彩色和左旋

在下图上,咱们晓得最初决裂当前,6 持续会向上进行交融,咱们晓得向上交融的节点肯定是红色的,所以咱们晓得 6 是红色的,但发现曾经没有父亲节点的时候,咱们将 6 变为彩色。

最开始的时候咱们想增加一个 42 的话,咱们间接让 42 当根节点就好了,而后咱们须要让 42 变为彩色节点。

这个时候咱们增加一个 37,能够间接放入到 42 左孩子的地位。

然而,如果咱们要插入的节点为根节点的右孩子的节点的时候,咱们须要进行左旋。

伪代码如下:

node.right = x.left;
x.left = node;
x.color = node.color;
node.color = red;

Java 代码如下:

    //   node                     x
    //  /   \     左旋转         /  \
    // T1   x   --------->   node   T3
    //     / \              /   \
    //    T2 T3            T1   T2
    private Node leftRotate(Node node){
        Node x = node.right;
        node.right = x.left;
        x.left = node;
        x.color = node.color;
        node.color = RED;
        return x;
    }

色彩翻转和右旋

最开始的时候咱们想增加一个 42 的话,咱们间接让 42 当根节点就好了,而后咱们须要让 42 变为彩色节点。

这个时候咱们增加一个 37,能够间接放入到 42 左孩子的地位。

如果这时候咱们再向红黑树中退出 66 节点,会变成如下图这样。

相应的在 2 - 3 树中是这个样子。

在红黑树中红色节点的意思就是和父节点是交融在一起的。

而 37 和 66 都是红色节点,阐明它们都和父亲节点是交融在一起的。

在 2 - 3 树中,咱们解决 4 节点的时候会进行决裂,决裂为 3 个 2 节点。

然而在红黑树中其实彩色节点就是 2 个节点,咱们不须要进行旋转操作,咱们只须要让 37 和 66 进行变色为彩色,就和 2 - 3 树中节点的性质是一样的了。

在 2 - 3 树中,咱们须要持续让 42 节点向下来找父亲节点进行交融,相应的在红黑树中咱们只须要让 42 变为红色即可。

这个就是 色彩翻转(flipColors)

     // 色彩翻转
    private void flipColors(Node node){
        node.color = RED;
        node.left.color = BLACK;
        node.right.color = BLACK;
    }

另外一种状况就是,咱们树增加成这个样子。咱们增加了 12 节点,其实能够了解为它们是交融在一起的。在 2 - 3 树种就是 4 节点,而后将它进行决裂。

然而在红黑树中咱们怎么变成这个样子呢?其实很简略,咱们只须要进行右旋转即可。

为了不便了解,咱们引入 T1 和 T2 虚构节点来演示。

咱们将 x 的右节点 t1 放到 node 的左孩子上,而后将 node 放在 x 的右孩子的地位即可。

别忘了,在这个时候 node(42)节点其实是交融在 x(37)节点上的,所以它要改为红色,37 要改为彩色。

对应的伪代码:

node.left = t1;
x.right = node;
x.color = node.color;
node.color = red;

最终咱们就能够失去和 2 - 3 树结构一样的红黑树了。

    //       node                            x
    //       / \                           /   \
    //      x   T2     向右旋转 (y)        y     node
    //     / \       - - - - - - - ->          / \
    //    y   T1                              T1  T2
    private Node rightRotate(Node node){
        Node x = node.left;
        node.left = x.right;
        x.right = node;
        x.color = node.color;
        node.color = RED;
        return x;
    }

在红黑树中增加新元素

其实还有一种状况咱们在后面没有讲到,就是在下图中增加一个元素 40 应该怎么做。

依据二分搜寻树的性质,咱们会将 40 增加进 37 的右孩子的地位。

然而这个时候曾经毁坏了红黑树的性质,所以咱们须要进行左旋。

这个时候咱们还须要进行右旋。

而后咱们须要将 40 变彩色,42 变为红色。

最初咱们进行色彩翻转即可维持红黑树的性质。

总结图片如下:

如果咱们增加的第三个元素是最小的话,咱们能够间接从右旋开始。

反之咱们增加的如果是最大的元素,咱们能够间接进行色彩翻转。

    // 判断节点 node 的色彩
    private boolean isRed(Node node){if(node == null) {return BLACK;}
        return node.color;
    }


    //   node                     x
    //  /   \     左旋转         /  \
    // T1   x   --------->   node   T3
    //     / \              /   \
    //    T2 T3            T1   T2
    private Node leftRotate(Node node){
        Node x = node.right;
        node.right = x.left;
        x.left = node;
        x.color = node.color;
        node.color = RED;
        return x;
    }

    // 色彩翻转
    private void flipColors(Node node){
        node.color = RED;
        node.left.color = BLACK;
        node.right.color = BLACK;
    }

    //       node                            x
    //       / \                           /   \
    //      x   T2     向右旋转 (y)        y     node
    //     / \       - - - - - - - ->          / \
    //    y   T1                              T1  T2
    private Node rightRotate(Node node){
        Node x = node.left;
        node.left = x.right;
        x.right = node;
        x.color = node.color;
        node.color = RED;
        return x;
    }

    // 向红黑树中增加新的元素(key, value)
    public void add(K key, V value){root = add(root, key, value);
        // 根节点为彩色
        root.color = BLACK;
    }

    // 向以 node 为根的红黑树中插入元素(key, value),递归算法
    // 返回插入新节点后红黑树的根
    private Node add(Node node, K key, V value){if(node == null){
            size ++;
            return new Node(key, value); // 默认插入红色节点
        }

        if(key.compareTo(node.key) < 0) {node.left = add(node.left, key, value);
        } else if(key.compareTo(node.key) > 0) {node.right = add(node.right, key, value);
        } else // key.compareTo(node.key) == 0
        {node.value = value;}

        // 右孩子为红色,左孩子为彩色,进行左旋
        if(isRed(node.right) && !isRed(node.left)){node = leftRotate(node);
        }
        // 左孩子为红色,并且左孩子的左孩子也是红色,进行右旋
        if(isRed(node.left) && isRed(node.left.left)){node = rightRotate(node);
        }
        // 色彩翻转
        if(isRed(node.left) && isRed(node.right)){flipColors(node);
        }

        return node;
    }

工夫复杂度剖析

红黑树相比于 AVL 树,其实是就义了平衡性的,红黑树并不齐全满足均衡二叉树的定义,红黑树的最大高度达到了 2logn 的高度,红色节点影响了红黑树的的平衡性。红黑树尽管就义了肯定的查问性能,然而在增删改操作的性能失去了补救,红黑树的综合性能还是要优于 AVL 树的。

正文完
 0