數據結構進階篇-紅黑樹

紅黑樹是一種平衡二叉搜索樹,它的自平衡機制簡單高效,所以常被用在許多底層設計當中,他的發明者之一Robert Sedgewick正是經典算法書籍《Algorithms》的做者。java

紅黑樹有哪些性質

首先紅黑樹是二叉搜索樹,因此它知足二叉搜索樹性質,除此以外紅黑樹還有下面5個性質:node

  1. 節點是紅色或黑色
  2. 根節點是黑色
  3. 每一個葉子節點都是NIL,而且是黑色的
  4. 每一個紅色節點的兩個子節點都是黑色
  5. 從任意一節點到它的每一個葉子節點的全部簡單路徑上都包含相同數目的黑色節點

還能夠推出下面這條性質:算法

  1. 假設紅黑樹的根節點到葉子節點的簡單距離的最大值爲MAX,最小值爲MIN,則有MAX <= 2 * MIN(不考慮NIL節點,這點很容易理解,假設根節點的黑高度是BH,由性質5可知MIN等於BH,結合性質二、三、4可得出,根節點到葉子節點的紅高度RH一定小於等於黑高度BH,全部MAX = BH + RH <= 2 * BH = 2 * MIN

從性質6能夠知道,紅黑樹只是一種接近平衡的二叉搜索樹,它不是嚴格的平衡二叉樹。因此紅黑樹的查詢要比AVL樹稍慢一丟丟,可是因爲紅黑樹維護平衡只須要旋轉和着色這兩種簡單操做,因此它的插入、刪除操做要比AVL樹快,綜合的統計效率要高於AVL樹。spa

下面這幅圖片展現了一顆紅黑樹,其中黑色的點表示葉子節點NIL。設計

紅黑樹

旋轉操做

旋轉操做其實並非紅黑樹特有的操做,早在AVL樹中就開始使用旋轉操做來維護樹的平衡了。我以爲旋轉操做的本質就是使樹傾斜,對節點X作左旋操做,其實就是使以X爲根節點的子樹向左傾斜,右旋就是向右傾斜,這樣的話,本來向右傾斜的樹,通過左旋以後就會變得平衡了,右旋也是一樣的道理。3d

這幅圖是上面那顆紅黑樹的一部分,展現了先對節點30作左旋操做,而後對節點38作右旋操做的過程 code

左旋和右旋

紅黑樹的查詢

紅黑樹的查詢操做和二叉搜索樹沒什麼區別,這裏就不贅述了,直接上一段代碼,關於紅黑樹的代碼我推薦直接看TreeMap的代碼,比較簡單明瞭。cdn

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;
複製代碼

紅黑樹的插入和刪除

紅黑樹的插入和刪除操做比較複雜,要考慮不少種狀況,爲了描述方便,我在這裏首先作一些約定,假設要插入或刪除的當前節點爲C,其父節點爲P,祖父節點爲G,叔叔節點爲U,兄弟節點爲S,兄弟節點的左子節點爲SL,兄弟節點的右子節點爲SR遞歸

插入節點

咱們約定插入的節點C是紅色的,爲何這樣呢?不妨思考一下,若是C是黑色的,一定會使樹傾斜。
此時咱們考慮兩種狀況,首先,若是P是黑色,直接插入節點就好了,不會違背紅黑樹的任何性質;若是P是紅色,插入節點後會有兩個連續的紅色節點C和P,會違背性質4,因此咱們須要作一些操做,從新平衡這棵樹。
很好,咱們一下就去除了一半的可能性,只須要考慮P是紅色狀況,一共有3個維度:圖片

  1. P是G的左孩子或右孩子,這兩種狀況是對稱的,考慮一種狀況能夠推出另外一種狀況。
  2. 在1的基礎上,再考慮U是紅色仍是黑色。
  3. 在2的基礎上,再考慮C是P的左孩子或右孩子。

這裏咱們只討論P是G的左孩子的狀況

1. 先考慮一種最基本的狀況,P是G的左孩子,且U是黑色,且C是P的左孩子

這種狀況下,只須要將P變爲黑色,G變爲紅色,對G作右旋操做便可。
下圖考慮了一種更通常的狀況(其實真正插入的時候,x、y、z和U都是NIL節點),很容易能夠看出子樹x、y、z和U三者的黑高度是相等的,此時並不違背性質5,因此單靠旋轉不能解決問題,須要從新着色,從新着色以後,P的左子樹黑高度變大了,即向左傾斜了,因此再對P作右旋轉操做,整棵樹恢復平衡,而且知足了性質4

紅黑樹插入1

2. P是G的左孩子,且U是黑色,且C是P的右孩子

這種狀況下,只須要對P作左旋操做,而後就變成了狀況1

紅黑樹插入2

3. P是G的左孩子,且U是紅色

這種狀況下,因爲U是紅色的,因此咱們不能像狀況1那樣直接把G變成紅色(這也是U分紅兩種狀況討論的緣由)。
此時須要將P和U變成黑色,而且將G變成紅色,作完這個操做以後,對於以G爲根節點的子樹來講,已是平衡的了,而且它的黑高度沒有發生變化。不須要考慮C是P的左孩子仍是右孩子,由於P變成了黑色,C是紅色,不會違背紅黑樹的性質。
那麼惟一會影響總體樹性質的只有節點G了,由於G變成了紅色,若是G的父節點也是紅色,就違背了性質4。此時咱們能夠發現,G變成了和C剛插入時相似的狀況(這也是我在狀況1中直接討論通常狀況的緣由),咱們只須要以G做爲當前節點再進行以上的操做便可,很明顯這是一個遞歸的操做。

紅黑樹插入3

上述3的遞歸操做可能會把根節點設置爲紅色,因此在平衡完整棵樹以後,將根節點設置爲黑色便可。

下面是TreeMap在插入節點後的平衡操做的代碼,我在覈心的地方添加了註釋:

private void fixAfterInsertion(Entry<K,V> x) {
    // 插入節點是紅色的
    x.color = RED;
    while (x != null && x != root && x.parent.color == RED) {
        // P是G的左孩子
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            // y是x的叔叔節點
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                // 狀況3:P是G的左孩子,且U是紅色,且C是P的左孩子
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                // 將當前節點指向祖父節點G,向上循環
                x = parentOf(parentOf(x));
            } else {
                // 狀況2:P是G的左孩子,且U是黑色,且C是P的右孩子
                if (x == rightOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateLeft(x);
                }
                // 狀況1:P是G的左孩子,且U是黑色,且C是P的左孩子
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateRight(parentOf(parentOf(x)));
            }
        } else {
            // P是G的右孩子和上面是相似的過程
            // ......
        }
    }
    // 最後面把根節點設置爲黑色
    root.color = BLACK;
}
複製代碼

刪除節點

刪除節點的操做比插入更加複雜,須要考慮更多種狀況。
首先從刪除節點的種類上分爲如下三種狀況:

  1. 刪除的節點沒有非NIL子節點
  2. 刪除的節點有且只有一個非NIL子節點
  3. 刪除的節點有兩個非NIL孩子(嚴格的內部節點)

其中狀況3可使用直接後繼法,轉換爲狀況1或2,就是將刪除節點C的直接後繼X的值拷貝到C,而後刪除X。這是二叉搜索樹刪除節點的通用作法。

下圖是三種查詢直接後繼的路徑圖,這裏只是爲了說明直接後繼,實際在本文中只會用到第一種狀況。

二叉樹的直接後繼

代碼以下:

static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
    if (t == null)
        return null;
    else if (t.right != null) {
        // 圖中的狀況一
        Entry<K,V> p = t.right;
        while (p.left != null)
            p = p.left;
        return p;
    } else {
        Entry<K,V> p = t.parent;
        Entry<K,V> ch = t;
        // 若是未進入循環體就是狀況二,不然就是狀況三
        while (p != null && ch == p.right) {
            ch = p;
            p = p.parent;
        }
        return p;
    }
}
複製代碼

1. 先看最簡單的一種狀況,刪除的節點有且只有一個非NIL子節點

此時刪除的節點C一定是黑色的,且它的非NIL孩子必定是紅色。不然以C爲根節點的子樹就違背了性質5。此時刪除C很簡單,直接將C的孩子設置爲黑色,替換C便可。

紅黑樹刪除1

2. 刪除的節點沒有非NIL子節點

此時若是C是紅色,這種狀況也很簡單,直接刪除C便可。
若是C是黑色,這是最複雜的一種狀況,由於C刪除以後,沒有子節點來補充C本來的位置,會影響到總體的平衡,下面討論一下這種狀況下不一樣的處理方法。這裏咱們只考慮C是P的左孩子的狀況,同理能夠得出C是P右孩子的狀況。

2.1 先考慮一個最簡單的狀況,C的兄弟S爲黑色,且S的右孩子SR爲紅色

由於C要被刪除,P的左邊黑高度會減1,因此咱們須要對P作左旋操做,而後S就到了P原來的位置,因此咱們把S設置爲P的顏色,將P設置爲黑色,將SR設置爲黑色。此時原來以P爲根節點的樹在C刪除後依舊保持平衡。這裏爲啥沒考慮S的左孩子SL呢?由於SL只能是紅節點或者NIL節點,不會影響平衡。下面圖中白色的節點表示能夠爲紅色也能夠爲黑色的意思。

紅黑樹刪除2

2.2 C的兄弟S爲黑色,且S的右孩子SR爲黑色(NIL),且SL爲紅色

這種狀況能夠將SL設置爲黑色,S設置爲紅色,而後對S作右旋操做,就變成了2.1

紅黑樹刪除2.2

2.3 C的兄弟S爲黑色,且S的左右孩子都爲黑色(NIL)

這種狀況下,若是P是紅色的,就比較簡單,直接刪除C,而後將S設置爲紅色,將P設置爲黑色就好了。
若是P是黑色的,就稍微麻煩一些,若是刪除了C,以P爲根節點的子樹的黑高度必然會下降1,此時只能向P的父輩節點尋求幫助,才能保持平衡了。
具體作法是,將C刪除,將S設置爲紅色,而後將P做爲當前節點,向上遞歸的考慮2.12.2就能夠了。

紅黑樹刪除3

2.4 C的兄弟S爲紅色

這種狀況下,SL和SR一定是黑色的非NIL節點。此時能夠先對P作左旋操做,由於P是黑色的,S是紅色的,因此須要從新着色,將P設置爲紅色,S設置爲黑色。而後咱們再來觀察節點C,發現C的兄弟變成了SL,而SL是黑色的,很顯然咱們已經將C的兄弟S爲紅色的狀況轉換成了S是黑色的狀況,根據SL的子節點X、Y的不一樣,會轉換成2.12.22.3三種狀況,以下圖所示:

紅黑樹刪除4

下面看一下Java的TreeMap的代碼實現:

private void deleteEntry(Entry<K,V> p) {
    // ...
    // 對於嚴格的內部節點,使用直接後繼法替換成另外兩種狀況
    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

    // Start fixup at replacement node, if it exists.
    Entry<K,V> replacement = (p.left != null ? p.left : p.right);

    if (replacement != null) {
        // 狀況1:刪除的節點有且只有一個非NIL子節點
        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;

        p.left = p.right = p.parent = null;

        if (p.color == BLACK)
            fixAfterDeletion(replacement); // 這裏其實只會設置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.
        // 狀況2: 刪除的節點沒有非NIL子節點
        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;
        }
    }
}
複製代碼

下面是刪除後的平衡操做:

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

            // 狀況2.4:C的兄弟S爲紅色
            if (colorOf(sib) == RED) {
                setColor(sib, BLACK);
                setColor(parentOf(x), RED);
                rotateLeft(parentOf(x));
                sib = rightOf(parentOf(x));
            }

            // 狀況2.3:C的兄弟S爲黑色,且S的左右孩子都爲黑色
            if (colorOf(leftOf(sib))  == BLACK &&
                colorOf(rightOf(sib)) == BLACK) {
                setColor(sib, RED);
                x = parentOf(x);
            } else {
                // 狀況2.2:C的兄弟S爲黑色,且S的右孩子SR爲黑色,且SL爲紅色
                if (colorOf(rightOf(sib)) == BLACK) {
                    setColor(leftOf(sib), BLACK);
                    setColor(sib, RED);
                    rotateRight(sib);
                    sib = rightOf(parentOf(x));
                }
                // 狀況2.1:C的兄弟S爲黑色,且S的右孩子SR爲紅色
                setColor(sib, colorOf(parentOf(x)));
                setColor(parentOf(x), BLACK);
                setColor(rightOf(sib), BLACK);
                rotateLeft(parentOf(x));
                x = root;
            }
        } else { // symmetric
            // ......
        }
    }

    setColor(x, BLACK);
}
複製代碼

紅黑樹和2-3-4樹的關係

2-3樹、2-3-4樹以及B類樹都是常見的多路查找樹,它們經過分裂和合並節點來維持絕對的平衡(這類樹的根節點到全部葉子節點的簡單距離都相等)。固然本文的重點不在此,這裏只是提一下紅黑樹和2-3-4樹的關係。
紅黑樹其實就是從2-3-4樹演變過來的,咱們將通常紅黑樹的黑色節點和其紅色子節點合併爲一個節點後,就能獲得一顆標準的2-3-4樹,相似的左傾紅黑樹和右傾紅黑樹都能轉換爲2-3樹,這裏就不展開討論了。
下圖就是本文最開始展現的那顆紅黑樹的2-3-4樹形態。

2-3-4樹
相關文章
相關標籤/搜索