1. 程式人生 > >紅黑二叉樹詳解及理論分析

紅黑二叉樹詳解及理論分析

  什麼是紅-黑二叉樹?

  紅-黑二叉樹首先是一顆二叉樹,它具有二叉樹的所有性質,是一種平衡二叉樹。普通二叉樹在生成過程中,容易出現不平衡的現象,即使是使用隨機演算法生成二叉樹,也是有一定概率生成不平衡的二叉樹. 如下圖所示 :

                                                        

為了解決二叉樹的不平衡問題,“大牛”們終於研究出了 紅-黑二叉樹(red-black binary tree),它總是生成像左圖那樣的平衡二叉樹。

   紅-黑二叉樹的資料結構

  1)葉子節點(leaf child),和 內部節點(internal node)

     紅黑二叉樹的每個節點都增加了2個指標,叫做 leaf child,它永遠存在,而且永遠都是 child,原二叉樹節點,叫做 internal node。

  2) 左旋,右旋, 重新著色(recolor)

       左旋,右旋, 重新著色(recolor) 是紅-黑二叉樹的三個基本操作。

   3) 規則

      3.1) 根節點和 葉子節點(leaf child) 節點必須是黑節點, 內部節點(internal node)非黑即紅。

      3.2) 從根節點開始到每條子路徑的葉子節點(leaf child),所有的黑節點數目相同。

      3.3)  紅節點的父親節點必須是黑節點。

      tree 1是一個標準的紅-黑樹,每條的路徑的黑節點數量都是 3。 tree 2和 tree 4是 tree 3 分別左旋,右旋後的結果。

       recolor 操作,設想一下樹 tree 1,可以把B節點 改變成紅色,D和E改變成黑色,這顆紅-黑樹同樣成立,這個操作就是 recolor。

   紅-黑二叉樹的設計思想

    如果不瞭解紅-黑樹的設計思想,死套相應的操作規則,是件很頭疼的事情,知其然而不知其所以然,也是工程師的大忌。可惜,在網上找了不少資料,都介紹紅-黑樹的工作流程。在這裡,我大膽地猜想一下紅黑樹的設計思想。

    紅-黑樹的設計思想應該是,利用 紅-黑這兩種節點顏色來追蹤二叉樹的平衡狀況, 重新著色(recolor)這個操作就是動態重新整理各節點顏色,如果發現樹開始出現不平衡狀況,就使用 左旋(left rotate)或者右旋(right rotate)來改變樹的結構。 把圖(tree 2) 和(tree 3) 反過來看,它們都是不平衡樹,對 (tree 2)進行右旋,或對(tree 4)進行左旋都能得到(tree 3)這樣的平衡樹。 左旋的實質是增加左樹的高度而減少右樹的高度,右旋反之。 這樣交替運用這三個操作,我們就可以構建一顆平衡的二叉樹。

   插入

    通過上面對設計思想的分析,插入的工作流程就非常簡單了。

    首先分析 父親節點 和 叔叔節點的顏色,判斷“父親”和“叔叔”的勢力是否平衡。如果平衡,則直接插入,然後進行recolor 操作,繼續最蹤插入後的情況。否則把"爺爺這顆子樹"(父親節點,爺爺節點 和 叔叔節點)左(右)旋以後再插入。

          如何判斷"爺爺"這顆子樹是否平衡呢?

          這就是紅-黑樹的核心了,通過 "父親"與"叔叔"的顏色來判斷。如果他們同時為紅色或者黑色,那麼"爺爺"這顆子樹是平衡的。否則,就不平衡。需要進行旋轉操作來平衡

這兩者的勢力。

  

   按照 紅-黑樹的規則,根節點 30 必須是黑色,然後,我們插入 20,為了不違反各分支,黑節點數相等的原則,20必須是紅色,接著再插入 紅色節點40,so good, so far. 現在 插入 節點11。現在問題出來了,按照紅節點必要有黑父親的規則, 11 和 20 出現了衝突, 這種情況下,需要進行 recolor 操作: 20 和 40 調整為黑色,同時把 11 的爺爺 30 調整為紅色,這樣可以保證 30 這顆子樹的黑節點數目不變。 如果 30 的父親節點恰好也是紅色,需要執行 插入紅色節點 30,如果 30的父親是黑節點,調整結束,如果 30是根節點根據規則,把它改成 黑節點,此時,各分支節黑點數 +1變成了2,數目仍然相等。如果插入的資料是平衡的,我們只需要重複執行,上述操作,插入紅節點,recolor。

 

   繼續插入 71, 發現 71的父親 62 這個節點沒有兄弟節點,很明顯以71的爺爺40作為根節點子樹,已經不平衡了,因為,平衡的情況應該是,62 有一個 紅色的兄弟節點——這樣的話,我們只需要遞迴執行 recolor 即可。 可惜,62沒有兄弟,於是,左旋一下 (40-62-71) 這顆樹,左旋以後, 黑色根節點40變成了孩子節點,需要把的黑色上提,同新的根節點62交換顏色,以保證 黑色節點數目不變。 就這樣交替執行下去,我們就會得到一顆平衡的二叉樹。


  繼續 插入 65,recolor 40, 71, 62這三個節點。 OK,我們繼續插入 78,64, 當插入64需要 recolor 65, 78 71 三個節點。 recolor 以後, 71 與 62同為紅色節點,違反了規則,此時,執行一個遞迴演算法,把71當成一個新接點插入。 現在讓我們來插入新節點 71,由於 62 與 20顏色不相同, 30為根節點的這顆樹不平衡,需要左一個 左旋的操作。

同時 62 與30 互換顏色。 現在,71的父親為根節點,遞迴結束,71 插入成功。

   上面的演示,包含了所有的插入的基本情況。 總結一下:

   1)插入的新節點,總是紅節點。

   2)  如果 插入出現紅節點衝突:

         1)父親,叔叔都是紅節點,表明樹處於平衡狀態,執行重新著色(recolor)操作,否則進行旋轉操作。

         2) 把新的爺爺節點(子樹根節點)當成一個新的節點, 插入該樹。 重新執行,插入新節點的操作。

   具體的 虛擬碼,很多地方都右介紹,我就不多說了。 虛擬碼裡面,把插入的各種狀況分得很細——一些變化的中間狀態,也當成了一個插入狀態,只是程式設計的需要,理論上,

從我們的剛才的分析中可以得出,實際的插入就2個狀態,平衡 or 不平衡——通過父親與叔叔的顏色來判斷。 平衡就recolor,不平橫就 rotate. 千萬不要被程式碼的分析給迷惑住了。

     刪除

      首先執行的是二叉樹的通用刪除方法, 找被刪節點M的子節點C來替換它,而不是直接刪除M,M被C替換後,刪除C即可!這樣就把一個刪除 父親節點的問題,轉變成了刪除子節點的問題。 子節點C的選擇原則是, 取右分支最左邊,或者 左分支最右邊的。 這樣,我們只需要研究 子節點C的刪除情況,子節點C 最多有一個孩子節點(C 是最左邊或者最右邊)。

     如下圖 d-1所示,要刪除M節點,可以先用 C或者D替換M,然後,在刪除C或者D。

     紅-黑二叉樹刪除節點,最大的麻煩是要保持 各分支黑色節點數目相等。 因為是刪除,所以不用擔心存在顏色衝突問題——插入才會引起顏色衝突。

 

     1) 直接刪除。

       1.1) M節點是紅節點,直接刪除。圖 d-2. M不可能帶有其它子節點——如子節點為黑,則違反黑節點數原則,為紅,則違反“顏色”原則。                

       1.2) M節點是黑節點帶有紅色子節點直接用子節點,則recolr 子節點為黑,替換M。

    2)  M節點無子節點,且與  P 節點同為黑色。

         如果刪除M則分支 P的黑節點數肯定 少一個,如果M有子節點,就直接用子節點頂上來(情況1)。現在只右考察一下M的兄弟N,如果它是紅色節點,那就好辦了,

利用 插入時 reclor逆操作,把紅節點變成黑節點,保持黑節點數目不變。

       先是進行一個左旋, 把紅節點當作新的根節點,然後,recolor,此時,P為紅,M和N1則為黑, 最後,再此 recolor, 把 M和N1變紅,P 變黑,現在可以直接刪除M了。

其實,仔細觀察一下,會發現它是 插入 recolor的逆過程。

       OK,現在我們利用 紅節點變黑, 刪除了一個黑節點。保持了黑節點數目不變的原則。

       如果 M 節點的兄弟節點是也是黑節點怎麼辦呢?

       這種情況,把M的兄弟S recolor 為紅,P節點分支減少一個 黑節點數, 然後,再把 P節點當作當前節點,遞迴"刪除",直到其它分支,也都recolor一個黑節點為紅節點,使黑節點數保持相同。

     :“刪除”加了引號,這個刪除不是指從樹中移除——這個過程是在最開始通過子節點替換完成的——而是指遞迴 recolor 一個黑節點。

    

        先recolor M的兄弟N,然後,“刪除” 當前節點P,  recolor p的兄弟 A3, 再"刪除"  A1,recolor A2,依次遞歸向上,每個分支減少一個 黑節點。

        這裡的 遞迴刪除 根節點 P,並不是真正意義上面的刪除,不同於刪除節點M,只是為了重用程式碼的結構而已。

       一些文章的虛擬碼裡面,把刪除也分成了好多種情況,其實 理論上就三種。 1) 直接刪除。 2) 旋轉,recolor 紅節點成黑節點。 3) 遞迴,recolor 黑節點為紅節點。

      後記

       這兩天研究 紅-黑樹,到網上查了好多資料,都是講述的操作流程,沒有文章講一下演算法的思想。看得人一頭霧水。我花了些時間,仔細研究了一下,感覺還是蠻簡單的。

真的很佩服演算法的設計者。寫出來供大家研究一下。

      參考資料

    1)   http://en.wikipedia.org/wiki/Red%E2%80%93black_tree

     2)   演算法導論。

     3)  google 一些文章