18、红黑树

内容来自刘宇波老师算法与数据结构体系课

1、红黑树介绍

参考 2 - 3 树

1、每个节点或者是红色的,或者是黑色的
2、根节点是黑色的
3、每一个叶子节点(最后的空节点)是黑色的
4、如果一个节点是红色的,那么他的孩子节点都是黑色的
5、从一个节点到任意叶子节点,经过的黑色节点是一样的

红黑树是保持 "黑平衡" 的二叉树
严格意义上,不是平衡二叉树,最大高度:2 * logN
红黑树添加和删除比 AVL 树快,查询比 AVL 树慢(它比 AVL 树更高)

image
image
image

2、添加元素图示

2.1、添加元素时,需要保持根节点为黑色节点

image

2.2、插入 "二节点" 的右侧

image

2.3、插入 "三节点" 的右侧

image

2.4、插入 "三节点" 的左侧

image

2.5、插入 "三节点" 的中间

image

2.6、向红黑树中添加元素总体流程图示

image

3、辅助函数

Node 新添加了一个成员变量 color,默认插入红色节点(新添加的元素一定是红色的)

辅助函数
1、判断节点 node 的颜色
2、保持根节点为黑色节点
3、左旋转
4、右旋转
5、颜色翻转

将一个元素插入到一个 "二节点" 中,使其融合形成 "三节点"
1、添加到左边
2、添加到右边:为了保证所有的红色节点都是左倾斜的,在这里需要通过左旋转来调整

将一个元素插入到一个 "三节点" 中,使其变为临时的 "四节点"
再拆解形成 3 个 "二节点",根节点继续向上,与它的父亲节点做融合
1、添加到右边:颜色翻转
2、添加到左边:右旋转、颜色翻转
3、添加到中间:左旋转、右旋转、颜色翻转
private static final boolean RED = true;
private static final boolean BLACK = false;

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

    public Node(K key, V value) {
        this.key = key;
        this.value = value;
        this.left = null;
        this.right = null;
        this.color = RED; // 默认插入红色节点
    }
}

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

/**
 * 对节点 node 进行左旋转操作, 返回旋转后新的根节点 x
 */
//   node                            x
//  /    \     向左旋转 (node)      /    \
// 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;
}

/**
 * 对节点 node 进行右旋转操作, 返回旋转后新的根节点 x
 */
//      node                           x
//     /    \     向右旋转 (node)     /    \
//    x     T3   - - - - - - - ->   T1   node
//  /   \                               /    \
// T1    T2                            T2    T3
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;
}

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

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

4、实现添加操作

添加操作
1、保持根节点为黑色节点
2、新添加的元素一定是红色的
3、保持 "黑平衡"

将一个元素插入到一个 "二节点" 中,使其融合形成 "三节点"
1、添加到左边
2、添加到右边:为了保证所有的红色节点都是左倾斜的,在这里需要通过左旋转来调整

将一个元素插入到一个 "三节点" 中,使其变为临时的 "四节点"
再拆解形成 3 个 "二节点",根节点继续向上,与它的父亲节点做融合
1、添加到右边:颜色翻转
2、添加到左边:右旋转、颜色翻转
3、添加到中间:左旋转、右旋转、颜色翻转

image

/**
 * 向红黑树中添加元素 (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 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;
}

5、红黑树

/**
 * Red-Black-Tree 红黑树
 * 红黑树是保持 "黑平衡" 的二叉树
 * 红黑树添加和删除比 AVL 树快, 查询比 AVL 树慢(它比 AVL 树更高)
 */
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;
        public Node right;
        public boolean color;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
            this.left = null;
            this.right = null;
            this.color = RED; // 默认插入红色节点
        }
    }

    private Node root;
    private int size;

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

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

    /**
     * 对节点 node 进行左旋转操作, 返回旋转后新的根节点 x
     */
    //   node                            x
    //  /    \     向左旋转 (node)      /    \
    // 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;
    }

    /**
     * 对节点 node 进行右旋转操作, 返回旋转后新的根节点 x
     */
    //      node                           x
    //     /    \     向右旋转 (node)     /    \
    //    x     T3   - - - - - - - ->   T1   node
    //  /   \                               /    \
    // T1    T2                            T2    T3
    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;
    }

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

    /**
     * 返回以 node 为根节点的红黑树中, 键为 key 所在的节点
     */
    private Node getNode(Node node, K key) {
        if (node == null) return null;

        if (key.compareTo(node.key) == 0) return node;
        if (key.compareTo(node.key) < 0) return getNode(node.left, key);
        return getNode(node.right, key);
    }

    /**
     * 向红黑树中添加元素 (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 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;
    }

    /**
     * 返回以 node 为根节点的红黑树中的最小元素所在的节点
     */
    private Node minimum(Node node) {
        if (node.left == null) return node;
        return minimum(node.left);
    }

    /**
     * 删除以 node 为根节点的红黑树的最小元素所在的节点, 并返回新的根节点
     */
    private Node removeMin(Node node) {
        if (node.left == null) {
            Node rightNode = node.right;
            node.right = null;
            size--;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    public V remove(K key) {
        Node node = getNode(root, key);
        if (node != null) {
            root = remove(root, key);
            return node.value;
        }
        return null;
    }

    /**
     * 以 node 为根节点的红黑树, 删除键为 key 所在的节点, 并返回新的根节点
     */
    private Node remove(Node node, K key) {
        if (node == null) return null;

        Node retNode;
        if (key.compareTo(node.key) < 0) {
            node.left = remove(node.left, key);
            retNode = node;
        } else if (key.compareTo(node.key) > 0) {
            node.right = remove(node.right, key);
            retNode = node;
        } else {
            if (node.left == null) {
                Node rightNode = node.right;
                node.right = null;
                size--;
                retNode = rightNode;
            } else if (node.right == null) {
                Node leftNode = node.left;
                node.left = null;
                size--;
                retNode = leftNode;
            } else {
                Node successor = minimum(node.right);
                successor.right = removeMin(node.right);
                successor.left = node.left;
                node.left = node.right = null;
                retNode = successor;
            }
        }

        return retNode;
    }

    public boolean contains(K key) {
        return getNode(root, key) != null;
    }

    public V get(K key) {
        Node node = getNode(root, key);
        return node != null ? node.value : null;
    }

    public void set(K key, V newValue) {
        Node node = getNode(root, key);
        if (node != null) node.value = newValue;
        else throw new IllegalArgumentException(key + " doesn't exist!");
    }

    public int getSize() {
        return size;
    }

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

6、性能总结

1、二分搜索树
对于完全随机的数据,普通的二分搜索树很好用!
缺点:极端情况退化成链表(或者高度不平衡)

2、AVL 树
对于查询较多的使用情况,AVL 树很好用!

3、红⿊树
红黑树牺牲了平衡性(2 * logN 的高度)
统计性能更优(综合增删改查所有的操作)

4、Splay Tree 伸展树
另一种统计性能优秀的树结构
局部性原理:刚被访问的内容下次高概率被再次访问
posted @ 2023-04-11 17:33  lidongdongdong~  阅读(31)  评论(0编辑  收藏  举报