美文网首页
红黑树解析

红黑树解析

作者: Catcher07 | 来源:发表于2018-07-11 14:13 被阅读0次

    AVL树的旋转

    https://blog.csdn.net/lemon_tree12138/article/details/50393548

    红黑树概念

    红黑树是一种近似平衡的二叉查找树,它能够确保任何一个节点的左右子树的高度差不会超过二者中较低那个的一倍。

    红黑树的特征

    • 树中的每一个节点是红色或者是黑色。
    • 根节点是黑色的。
    • 每个叶子节点是黑色的(NIL节点)。
    • 每个红色的节点后面必然是黑色的子节点(红节点不能连续)
    • 对于任意节点到树的尾端节点(NIL)路径中包含的黑色节点数目相同。


      image.png

      红黑树相对于AVL树来说,牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树。

    红黑树数据结构定义

        static final class Entry<K,V> implements Map.Entry<K,V> {
            K key;
            V value;
            Entry<K,V> left;
            Entry<K,V> right;
            Entry<K,V> parent;
            boolean color = BLACK;
            ...
    }
    

    红黑树的旋转

    1. 红黑树的左旋
      image.png
      image.png
      代码实现

    TreeMap的左旋的代码


    image.png

    Java JDK1.8源码

        private void rotateLeft(Entry<K,V> p) {
            if (p != null) {
                Entry<K,V> r = p.right;
                p.right = r.left;
                if (r.left != null)
                    r.left.parent = p;
                r.parent = p.parent;
                if (p.parent == null)
                    root = r;
                else if (p.parent.left == p)
                    p.parent.left = r;
                else
                    p.parent.right = r;
                r.left = p;
                p.parent = r;
            }
        }
    

    python实现

    def rotateLeft(p, root):
        '''
        :type p: Entry 要旋转的节点
        '''
        if p is not None:
            r = p.right
            p.right = r.left
            if r.left is not None:
                r.left.parent = p
            r.parent = p.parent
            if p.parent is None:
                root = r
            elif p == p.parent.left:
                p.parent.left = r
            else:
                p.parent.right = r
            r.left = p
            p.parent = r
    
    1. 红黑树的右旋


      image.png
      image.png

    Java JDK1.8源码

        private void rotateRight(Entry<K,V> p) {
            if (p != null) {
                Entry<K,V> l = p.left;
                p.left = l.right;
                if (l.right != null) l.right.parent = p;
                l.parent = p.parent;
                if (p.parent == null)
                    root = l;
                else if (p.parent.right == p)
                    p.parent.right = l;
                else p.parent.left = l;
                l.right = p;
                p.parent = l;
            }
        }
    

    这里不详细分析右旋,右旋和左旋的原理基本是一样的。

    方法剖析

    1. get()方法
      get(Object key)方法根据指定的key值返回对应的value,该方法调用了getEntry(Object key)得到相应的entry,然后返回entry.value。因此getEntry()是算法的核心。算法思想是根据key的自然顺序(或者比较器顺序)对二叉查找树进行查找,直到找到满足k.compareTo(p.key) == 0的entry。


      image.png
       final Entry<K,V> getEntry(Object key) {
            // Offload comparator-based version for sake of performance
            if (comparator != null)
                return getEntryUsingComparator(key);
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            Entry<K,V> p = root;
            while (p != null) {
                int cmp = k.compareTo(p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
            return null;
        }
    
    1. put()方法
      put(K key, V value)方法是将指定的key, value对添加到map里。该方法首先会对map做一次查找,看是否包含该元组,如果已经包含则直接返回,查找过程类似于getEntry()方法;如果没有找到则会在红黑树中插入新的entry,如果插入之后破坏了红黑树的约束,还需要进行调整(旋转,改变某些节点的颜色)。
        public V put(K key, V value) {
            Entry<K,V> t = root;
            if (t == null) {
                compare(key, key); // type (and possibly null) check
    
                root = new Entry<>(key, value, null);
                size = 1;
                modCount++;
                return null;
            }
            ...
            ...
            else {
                if (key == null)
                    throw new NullPointerException();
                @SuppressWarnings("unchecked")
                    Comparable<? super K> k = (Comparable<? super K>) key;
                do {
                    parent = t;
                    cmp = k.compareTo(t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        return t.setValue(value);
                } while (t != null);
            }
            Entry<K,V> e = new Entry<>(key, value, parent);
            if (cmp < 0)
                parent.left = e;
            else
                parent.right = e;
            fixAfterInsertion(e);
            size++;
            modCount++;
            return null;
    }
    

    上述代码的插入部分并不难理解:首先在红黑树上找到合适的位置,然后创建新的entry并插入(当然,新插入的节点一定是树的叶子)。难点是调整函数fixAfterInsertion(),前面已经说过,调整往往需要1.改变某些节点的颜色,2.对某些节点进行旋转。

    image.png
    调整函数fixAfterInsertion()的具体代码如下,其中用到了上文中提到的rotateLeft()和rotateRight()函数。通过代码我们能够看到,情况2其实是落在情况3内的。情况4~情况6跟前三种情况是对称的,因此图解中并没有画出后三种情况,读者可以参考代码自行理解。
    红黑树调整函数
        private void fixAfterInsertion(Entry<K,V> x) {
            x.color = RED;
    
            while (x != null && x != root && x.parent.color == RED) {
                if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                    Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                    if (colorOf(y) == RED) {
                        // 对应图4
                        setColor(parentOf(x), BLACK);
                        setColor(y, BLACK);
                        setColor(parentOf(parentOf(x)), RED);
                        x = parentOf(parentOf(x));
                    } else {
                        if (x == rightOf(parentOf(x))) {
                            // 把图6变成图5
                            x = parentOf(x);
                            rotateLeft(x);
                        }
                        // 对应图5
                        setColor(parentOf(x), BLACK);
                        setColor(parentOf(parentOf(x)), RED);
                        rotateRight(parentOf(parentOf(x)));
                    }
                } else {
                    Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                    if (colorOf(y) == RED) {
                        // 对应图4
                        setColor(parentOf(x), BLACK);
                        setColor(y, BLACK);
                        setColor(parentOf(parentOf(x)), RED);
                        x = parentOf(parentOf(x));
                    } else {
                        if (x == leftOf(parentOf(x))) {
                            //把图8变成图7
                            x = parentOf(x);
                            rotateRight(x);
                        }
                        // 对应图7的操作
                        setColor(parentOf(x), BLACK);
                        setColor(parentOf(parentOf(x)), RED);
                        rotateLeft(parentOf(parentOf(x)));
                    }
                }
            }
            root.color = BLACK;
        }
    

    旋转图示

    1.红父
    如果新点的父结点为红色,这时就需要进行一系列操作以保证整棵树红黑性质。如图3所示,由于父结点为红色,此时可以判定,祖父结点必定为黑色。这时需要根据叔父结点的颜色来决定做什么样的操作。青色结点表示颜色未知。由于有可能需要根结点到新点的路径上进行多次旋转操作,而每次进行不平衡判断的起始点(我们可将其视为新点)都不一样。所以我们在此使用一个蓝色箭头指向这个起始点,并称之为判定点。

    image.png

    1.1 红叔

    • 当叔父结点为红色时,如图4所示,无需进行旋转操作,只要将父和叔结点变为黑色,将祖父结点变为红色即可。但由于祖父结点的父结点有可能为红色,从而违反红黑树性质。此时必须将祖父结点作为新的判定点继续向上进行平衡操作。


      image.png

      需要注意,无论“父”在“叔”的左边还是右边,无论“新”是“父”的左孩子还是右孩子,它们的操作都完全一样。

    1.2 黑叔
    当叔父结点为黑色时,需要进行旋转,以下图示了所有的旋转可能

    • 情形1:


      image.png
    • 情形2:


      image.png
    • 情形3:


      image.png
    • 情形4:


      image.png

    红黑树的删除

    根据红黑树的性质可以得出以下两个结论:
    1、 删除操作中真正被删除的必定是只有一个红色孩子或没有孩子的结点。
    2、 如果真正的删除点是一个红色结点,那么它必定是一个叶子结点。

    如图10所示,除了情况(a)外,其他任一种况结点N都无法满足红黑树性质。


    image.png

    https://blog.csdn.net/sun_tttt/article/details/65445754

    参考自

    http://www.cnblogs.com/abatei/archive/2008/12/17/1356565.html
    https://www.cnblogs.com/CarpenterLee/p/5503882.html

    相关文章

      网友评论

          本文标题:红黑树解析

          本文链接:https://www.haomeiwen.com/subject/mdjhpftx.html