【数据结构】之红黑树

1、引言

红黑树是一种平衡的二叉树,其在二分搜索树的基础上添加了一些特性,保证其不会退化成链表。java

一、红黑树的定义

  1. 知足二分搜索树的基本性质
  2. 每一个节点要么是红色的,要么是黑色的
  3. 根节点是黑色的
  4. 每个叶子节点(最后的空节点)是黑色的
  5. 若是一个节点是红色的,那么它的孩子节点都是黑色的
  6. 从任意一个节点到叶子节点,通过的黑色节点是同样的

说明:以上红黑树的定义看完很快就会忘记,疑惑点在于为何要这么定义。为何有的节点是红色或是黑色。为何这么几条限制就能保证红黑树的平衡性。这些疑问点在看完下面对于“2-3树”的介绍后会逐渐变得明朗,“2-3树”和红黑树是等价的,红黑树的各个特性能够类比于“2-3树”。node

2、2-3树

一、什么是2-3树

2-3树是一颗绝对平衡的树,从根节点到任意叶子节点所应该的节点数量是相等的;它知足二分搜索树的基本性质;有的节点仅包含一个元素,它容许有2个孩子节点,被称为“2节点”;有的节点包含两个元素,它容许有三个孩子节点,被称为“3节点”。
2-3树.png算法

二、2-3树的定义

  • 知足二分搜索树的基本性质
  • 节点能够存放一个元素或者两个元素
  • 每一个节点有2个(2节点)后者3个孩子(3节点)

2-3树的节点.png

三、2-3树维持绝对平衡

一、元素添加到2节点

元素添加到2节点.png

说明:元素添加到2-3树的2节点直接融合成一个3节点便可。性能

二、元素添加到3节点

元素添加到3节点.png

说明:元素添加到2-3树的3节点会临时融合成“4节点”,而后会裂变成三个2节点,以其中一个节点为根,裂变后仍然知足二分搜索树的性质。this

三、元素添加到3节点,且父亲节点为2节点

元素添加到3节点,且父亲节点为2节点.png

说明:元素添加到2-3树的3节点会临时融合成“4节点”,而后会裂变成三个2节点,以其中一个节点为根;此临时4节点裂变后产生的新子树高度增长了1,破坏了绝对平衡性,因而这颗新子树的根须要继续向其父亲节点融合,融合后新子树增长的高度被抹平,从新保持绝对平衡。spa

四、元素添加到3节点,且父亲节点为3节点

元素添加到3节点,且父亲节点为3节点.png

说明:2-3树维护绝对平衡,靠的就是融合、裂变的操做,融合后树的高度保持变后者高度下降,而裂变操做后会增长树的高度,所以裂变后会伴随着融和操做,融合操做中和裂变操做。从而保证了2-3的绝对平衡。3d

3、红黑树和2-3树

一、红黑树与2-3树的等价性

红黑树与2-3树的节点等价性.png

红黑树和2-3树.png

红黑树和2-3树类比.png

  • 红黑树用黑色节点表示2-3树的2节点
  • 红黑树用红色节点表示与其祖先节点融合组成3节点

说明:红黑树之因此定义节点的红黑颜色,实际上就是模拟2-3树的2节点或者3节点。咱们再来回顾一下关于开头的红黑树定义,下面会作个简单说明。code

  • 为何每一个节点要么是红色的,要么是黑色的?

说明:模拟2-3树的2节点和3节点只须要两种颜色便可,红色节点表示与其父亲节点融合。blog

  • 为何根节点是黑色的?

说明:根节点做为红黑树的根,不须要向上融合了。继承

  • 为何一个节点是红色的,那么它的孩子节点都是黑色的?

说明:由于一个红色节点与其父亲节点已经组成了相似于2-3树的3节点了,若是其孩子节点依然是红色的,那就构成了2-3树中的临时“4节点”,此时2-3树要进行裂变和融合操做保证绝对平衡性。那么相似于2-3树,红黑树有连续两个节点是红色的话,红黑树须要进行旋转操做(相似于2-3树的裂变操做)配合颜色翻转操做(相似于2-3树的融合操做),以使得红黑树保持平衡性。连续连续两个红色节点也是红黑树出发自平衡的触发点,因此不会出现连续两个红色节点。

  • 为何从任意一个节点到叶子节点,通过的黑色节点是同样的?

说明:因为红黑树是模拟于2-3树,而2-3树是绝对平衡的树,因此除去表示融合的红色节点,红黑树中的黑色节点也是绝对平衡的,红黑树也被称为是“黑平衡”的二叉树,所以从红黑树的任意节点出发到叶子节点,所经历的黑色节点数是一致的。

4、红黑树相关操做

红黑树在添加元素、删除元素后会影响树的平衡性,可能会破坏红黑树的定义。相似于2-3树的平衡操做(裂变节点、融合节点),红黑树也有其平衡操做,左右旋转相似于2-3树的裂变节点;颜色翻转相似于2-3树的融合节点。
红黑树相关操做.png

说明:如下会实现一个左倾红黑树,在递归算法实现的红黑树添加操做中,当递归到深层的叶子节点融合元素(红色节点)后可能形成不平衡,须要递归逐层向上维护节点状态。

一、红黑树左旋操做

  • 左倾左旋操做前

左旋操做前.png

说明:左倾红黑树规定节点的右孩子不能是红色节点,须要左旋操做。

  • 左旋操做先后

左旋操做先后.png

说明:左旋操做后当前子树的根节点发生了变化,当前的根节点须要继承保留原有根节点的颜色,同时原来的根节点变成红色节点。若是原有根节点是红色节点,那么上层须要继续进行相关操做。

二、红黑树的右旋操做

  • 右旋操做前

右旋操做前.png

说明:连续两个节点为红色节点,违反了红黑树的定义,相似于造成了2-3树的临时4节点,此时红黑树的平衡被破坏,须要再平衡操做。

  • 右旋操做后

右旋操做后.png

说明:右旋操做后,发现节点状态依然相似于2-3树的临时4节点状态,须要继续裂变和融合操做,而红黑树此种场景只须要向上融合便可,即须要颜色翻转,使x节点变成红色节点表示向上融合。

三、颜色翻转操做

  • 颜色翻转前

颜色翻转前.png

  • 颜色翻转后

颜色翻转后.png

总结:红黑树模拟于2-3树也等价于2-3树。红黑树的左右旋转等价于2-3树的裂变节点操做;红黑树的颜色翻转等价于2-3树的融合节点操做;不一样点在于红黑树的左右旋转会能够下降子树的高度,而2-3树的裂变节点操做会增长子树的高度;红黑树的颜色翻转操做不会改变子树的高度,是一个抽象的融合操做,而2-3树的融合操做能够下降子树高度。最终红黑树也会趋于平衡,而2-3会保持绝对平衡。

5、红黑树的实现

一、左倾红黑树的实现

import java.util.ArrayList;

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 getSize(){
        return size;
    }

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

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

    /**
     * 节点左旋转操做
     *   node                     x
     *  /   \     左旋转         /  \
     * T1   x   --------->   node   T3
     *     / \              /   \
     *    T2 T3            T1   T2
     * @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;
    }

    /**
     * 右旋转操做
     *     node                   x
     *    /   \     右旋转       /  \
     *   x    T2   ------->   y   node
     *  / \                       /  \
     * y  T1                     T1  T2
     * @param node
     * @return
     */
    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
     */
    private void flipColors(Node node) {
        node.color = RED;
        node.left.color = BLACK;
        node.right.color = BLACK;
    }

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

    /**
     * 向以node为根的二分搜索树中插入元素(key, value),递归算法
     * 返回插入新节点后二分搜索树的根
     * @param node 树节点
     * @param key 元素key
     * @param value 元素value
     * @return 返回插入新节点后二分搜索树的根
     */
    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;
    }

    /**
     * 返回以node为根节点的二分搜索树中,key所在的节点
     * @param node 节点
     * @param key 元素key
     * @return
     */
    private Node getNode(Node node, K key){

        if(node == null)
            return null;

        if(key.equals(node.key))
            return node;
        else if(key.compareTo(node.key) < 0)
            return getNode(node.left, key);
        else // if(key.compareTo(node.key) > 0)
            return getNode(node.right, key);
    }

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

    public V get(K key){

        Node node = getNode(root, key);
        return node == null ? null : node.value;
    }

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

        node.value = newValue;
    }

    /**
     * 返回以node为根的二分搜索树的最小值所在的节点
     * @param node
     * @return
     */
    private Node minimum(Node node){
        if(node.left == null)
            return node;
        return minimum(node.left);
    }
}

6、时间复杂度分析

红黑树相比于AVL树,实际上是牺牲了平衡性的,红黑树并不彻底知足平衡二叉树的定义,红黑树的最大高度达到了2logn的高度,红色节点影响了红黑树的的平衡性。红黑树虽然牺牲了必定的查询性能,可是在增删改操做的性能获得了弥补,红黑树的综合性能仍是要优于AVL树的。

相关文章
相关标签/搜索