【数据结构】红黑树原理介绍及实现

1,211 阅读4分钟

《算法导论》中的红黑树

红黑树是二分搜索树中的一种。

满足以下性质:

1.每个节点是红色或者黑色。

2.根节点是黑色。

3.每个叶子节点是黑色。

4.如果一个节点是红色,其孩子节点都是黑色。

5.从任意一个节点到叶子节点经过的黑色节点相同。

对于初学者,直接给出这样的5个性质会难以理解,因此我们先来了解2-3树。

2-3树

红黑树与2-3树是等价的。

满足二分搜索树的基本性质。

节点可以存放一个或者两个元素,如图

2-3树是一棵绝对平衡的树。

2-3树如何维持绝对平衡

2-3树添加节点永远不会添加到一个空的位置,只能和最后找到的叶子节点融合。

对于只有一个节点的2-3树(设这个节点值为42)在添加新节点(设值为37)时首先检查根节点的左子树,若为空则融合两个节点。若继续添加一个值为12的节点,由于新节点不能添加到一个空的位置,所以只能继续和根节点融合,暂时形成一个4节点,如图

然后将其分裂,形成一棵由3个2节点组成的平衡树,如图

继续添加一个值为18的节点,按照规则将其与12节点融合

然后再添加一个值为6的节点,如果继续按照上述的流程将其融合到12的左侧然后分裂就会形成下图的树

这时我们可以发现这棵树已经不是一棵绝对平衡树了,这时应该将12节点与它的父亲节点进行融合,形成一个3节点。

后续的添加操作重复上述过程即可。

红黑树

红黑树与2-3树不同的一点在于红黑树的每个节点只能存一个元素,但是我们可以通过组合的方式表示出2-3树中的3节点。

红色的边表示两端的节点是并列的关系,红色的节点表示该节点和它的父亲节点一起表示2-3树中的3节点。

规定所有的红色节点都是向左倾斜的。

红黑树是保持“黑平衡”的二叉树,严格意义上不是平衡二叉树,对于n个节点的红黑树其最大高度为2logn。增删改一个元素的时间复杂度都是O(logn)。

基本数据结构

public class RBTree<K extends Comparable<K>, V> {

    private static final boolean RED = true;
    private static final boolean BLACK = false;

    private class Node{
        public K key;
        public V value;
        public Node left, right;
        public boolean color;

        public Node(K key, V value){
            this.key = key;
            this.value = value;
            left = null;
            right = null;
            color = RED;
        }
    }

    private Node root;
    private int size;

    public RBTree(){
        root = null;
        size = 0;
    }

    public int size(){
        return size;
    }

    public boolean isEmpty(){
        return size == 0;
    }

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

添加新元素

要始终保持根节点为黑色。

如图当新添加节点在根节点的右侧时,违背了红色节点都在左侧的规定,此时我们需要进行左旋转。

左旋转以后的红黑树。

旋转过程:

假设37节点为node,42节点为x

node.right = x.left;
x.left = node;
//对颜色进行维护
x.color = node.color;
node.color = RED;

具体实现

/**
 * 左旋转操作
 * @param node
 * @return
 */
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;
}

添加元素时最复杂的情况是新加入的元素在原来两个节点的中间,这种情况的操作过程如图所示

维护的时机:添加节点后回溯向上维护。

完整的添加新元素的逻辑

/**
 * 颜色翻转
 * @param node
 */
private void flipColors(Node node){

    node.color = RED;
    node.left.color = BLACK;
    node.right.color = BLACK;
}

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;
}

/**
 * 左旋转操作
 * @param node
 * @return
 */
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;
}

public void add(K key, V value){
    root = add(root, key, value);
    root.color = BLACK;
}

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 {
        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高度),统计性能更优(增删改查所有操作)

统计性能更优的树:伸展树SplayTree(原理是刚被访问的节点下次高概率被再次访问)。

红黑树的应用

java.util中的TreeMap和TreeSet都基于红黑树。

Written by Autu

2019.7.5