1. 程式人生 > >數據結構(四)--- 紅黑樹(RedBlock-Tree)

數據結構(四)--- 紅黑樹(RedBlock-Tree)

rabl real-time 消息 ren linu 轉變 數據結構 replace 算法

文章圖片來自鄧俊輝老師課件

先提幾個問題去思考學習本文 :

  • 紅黑樹和2-4樹(B-Tree)很像,那麽它存在的動機又是什麽呢
  • 插入和刪除操作的邏輯又是怎麽樣的,時間和空間復雜度可以達到怎麽樣
  • 和 AVL 對象有什麽區別呢

概述

定義

技術分享圖片

我們可以看到紅黑樹有4條重要的定義,這4條定義保證了這個平衡樹。下面我們看一下它和B-Tree的聯系。

技術分享圖片

從這個結構上說,我們可以知道B-Tree相比於紅黑樹,紅黑樹需要維護一個顏色這樣的屬性,需要空間,而同時紅黑樹搜索時可以

像二叉樹一般查找,而B-Tree每一個超級節點需要維護多個關鍵碼。這方面查看 RST_WIKI 這裏的分析。

但是這樣的樹是BBST嗎?

技術分享圖片

上面的數學推算已經向我證明,平均的深度為 h = O(LogN)

動機

Persistant Structure 一致性

下面介紹了紅黑樹為什麽適應一致性結構。技術分享圖片

技術分享圖片

時間和空間復雜度紅黑樹可以適應條件。同時拓撲結構上,無論是插入還是刪除,都可以不超過O(1).技術分享圖片

最壞情況下保證插入和刪除,查找

這裏引用 wiki 上的一段話說明紅黑樹在這方面的表現。

Red–black trees offer worst-case guarantees for insertion time, deletion time, and search time. Not only does this make them valuable in time-sensitive applications such as real-time applications, but it makes them valuable building blocks in other data structures which provide worst-case guarantees; for example, many data structures used in computational geometry can be based on red–black trees, and the Completely Fair Scheduler used in current Linux kernels and epoll system call implementation[19]

uses red–black trees.

時間空間復雜度

技術分享圖片

出處見參考資料

插入原理解析

我們定義插入的節點為紅色,那麽就有一種情況,雙紅,這違反了我們前面紅黑樹的定義,下面介紹如何解決雙紅問題。

RR-1

第一種情況 u 節點(uncle節點)是黑色的。

技術分享圖片

技術分享圖片

我們可以使用3+4實現,讓樹消除雙紅現象,3+4實現可以參考這篇文章 : 3+4

RR-2

u節點是紅色的情況下,最終可能導致樹高度+1

技術分享圖片

技術分享圖片技術分享圖片

插入歸納

技術分享圖片

刪除原理解析

我們思考一下,假如先不看顏色,那麽二叉樹的刪除算法中,考察三種情況

  • 擁有其中一個子節點
  • 無子節點
  • 擁有左右子節點

其中第一和第二種情況很好處理,直接刪除即可,有其中一個子節點需要重新連接一下父節點。第三種需要找出繼承節點,然後替

換掉刪除節點。繼承節點簡單點解釋就是右樹中最小的一個。

好了,那麽此時我們再來思考一下顏色的問題,我們知道紅黑樹不能紅紅相聯,且每個底部節點到根節點黑節點的數量都相等,要

是刪除節點是上面第一種情況和第二種情況且刪除節點是紅節點,直接刪除對樹的平衡沒有情況影響。

那麽要是刪除節點和繼承節點一黑一白呢,我們只需將刪除後,將繼承節點染黑就可以了,見下圖。

技術分享圖片

那麽刪除節點和繼承節點都是黑的情況呢?

雙黑缺陷

技術分享圖片

可以看到,當刪除節點和替代節點都為黑節點,刪除會產生下溢(下溢的概念可以參考上篇B-樹),需要考察繼承節點的父節點p和

兄弟節點 s ,下面分四種情況處理 :

BB-1

技術分享圖片

a’ 和 b’ 都是B-Tree 的撲拓結構,可以看到當s擁有一個紅節點時,產生下溢的節點通過旋轉,向兄弟節點借來了一個節點,從而

達到了平衡。而從 a 到 b 的過程,需要借助的是3+4操作。

BB-2R

技術分享圖片

BB-2R的情況,就相當於B-Tree的合並,而我們看到最終的撲拓結構是不變的,只需要進行染色,那麽當父節點被拿走了一個,是否會產生下溢呢?不會,因為在父節點中有紅節點,那麽左或右必有黑節點。

BB-2B

技術分享圖片

同樣也是合並操作,此時不是像BB-2R一樣是紅色節點了,那麽就有可能引發下沈下溢,那麽是不是會像AVL一樣進行LogN次的旋轉操作呢?不會,從a到b,我們可以他們的撲拓結構沒有改變,改變的只是顏色,所以不會發生LogN次的旋轉。時間復雜度依舊是O(1).

BB-3

技術分享圖片

可以看到,經過了左旋或是右旋,還有變色,由a到b後,黑高度依舊異常,可以有一個好消息就是,經過旋轉變成了我們之前處理

的情況一樣,即 BB-1 或是 BB-2R ,不會是 BB-2B的原因是 x 有個新的兄弟節點 s’ ,而且 p 為紅節點。

至此,我們雙黑的情況全部介紹完畢。

歸納總結和AVL的對比

技術分享圖片

代碼實現

代碼實現我們以java中的TreeMap 來解釋。本文只會介紹刪除操作。

   /**
     * Removes the mapping for this key from this TreeMap if present.
     *
     * @param  key key for which mapping should be removed
     * @return the previous value associated with {@code key}, or
     *         {@code null} if there was no mapping for {@code key}.
     *         (A {@code null} return can also indicate that the map
     *         previously associated {@code null} with {@code key}.)
     * @throws ClassCastException if the specified key cannot be compared
     *         with the keys currently in the map
     * @throws NullPointerException if the specified key is null
     *         and this map uses natural ordering, or its comparator
     *         does not permit null keys
     */
    public V remove(Object key) {
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        deleteEntry(p);
        return oldValue;
    }



   /**
     * Returns this map‘s entry for the given key, or {@code null} if the map
     * does not contain an entry for the key.
     *
     * @return this map‘s entry for the given key, or {@code null} if the map
     *         does not contain an entry for the key
     * @throws ClassCastException if the specified key cannot be compared
     *         with the keys currently in the map
     * @throws NullPointerException if the specified key is null
     *         and this map uses natural ordering, or its comparator
     *         does not permit null keys
     */
    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;
    }



     /**
     * Delete node p, and then rebalance the tree.
     */
    private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;

        // If strictly internal, copy successor‘s element to p and then make p
        // point to successor.
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children


        //到了這裏,無論p是有幾個孩子,p這個變量變成了要刪除的節點
        //要是 p有兩個child,會進入上面那個if,p變為了繼承節點
        // Start fixup at replacement node, if it exists.
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) { //只存在一個子節點
            // Link replacement to parent  重新連接父節點,需要刪除的節點置為 null 
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;

            // Fix replacement 開始修復,判斷是不是黑節點是因為紅節點直接刪除沒有影響 : 
            // 每個底部節點到根節點的黑色節點數量相等
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node. 該樹只有一個節點
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.  沒有子節點
            if (p.color == BLACK)         //繼承節點為黑 
                fixAfterDeletion(p);

            if (p.parent != null) {       //繼承節點為紅,直接刪除 
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }


    // 實際就是解決雙黑節點的問題
    /** From CLR */
    private void fixAfterDeletion(Entry<K,V> x) {
        while (x != root && colorOf(x) == BLACK) { //非根且為黑節點
            if (x == leftOf(parentOf(x))) {        
                Entry<K,V> sib = rightOf(parentOf(x));  //取右兄弟節點

                if (colorOf(sib) == RED) {                 
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));			  
                    sib = rightOf(parentOf(x));
                }                                          

                if (colorOf(leftOf(sib))  == BLACK &&      
                    colorOf(rightOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(rightOf(sib)) == BLACK) {  
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));   
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(sib), BLACK);
                    rotateLeft(parentOf(x));
                    x = root;
                }
            } else { // symmetric                        //和前面是對稱的
                Entry<K,V> sib = leftOf(parentOf(x));    // x是左節點

                if (colorOf(sib) == RED) {               //假如是BB-3,旋轉後只能是 BB-1 或者是 BB-2R
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    sib = leftOf(parentOf(x));
                }                                       

                if (colorOf(rightOf(sib)) == BLACK &&   //BB-2R 或是  BB-2B 
                    colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {  //BB-1 中 紅在右邊
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateLeft(sib);
                        sib = leftOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);
                    rotateRight(parentOf(x));
                    x = root;                           //跳出while
                }
            }
        }

        setColor(x, BLACK);
    }

參考資料

  • 鄧俊輝老師數據結構課程
  • RST_WIKI

數據結構(四)--- 紅黑樹(RedBlock-Tree)