思考紅黑樹

小灰馬發表於2013-08-22

前言

        網上講紅黑樹的文章比較多,參考 維基百科--紅黑樹 《教你徹底理解紅黑樹》  等等。大概掃了掃,在講紅黑樹的插入和刪除操作的時候,主要集中精力在講插入和刪除的各種複雜情況,然後再畫幾個圖出來,作者就以為講得很清楚很牛逼了。其實我覺得,講得非常爛,演算法最重要的是它背後的思想,或者說它背後一些規律。前面提到的兩篇文章都在講“這個演算法是什麼”,但是“這個演算法為什麼是這樣”壓根就沒仔細分析,例如紅黑樹刪除的時候,為什麼主要的那四種情況下要選擇那樣處理,我覺得這非常重要。當然我不敢保證我講得就不爛,我也只是根據自己的總結分析,去猜測“為什麼是這樣”的答案,歡迎拍磚。
       下面先在文章前面先把一些基本的東西貼一貼,引自維基百科。另外建議讀者在看紅黑樹演算法的時候,一定要自己親自動手畫。

紅黑樹的概念

紅黑樹是一種自平衡二叉查詢樹,是在電腦科學中用到的一種資料結構,典型的用途是實現關聯陣列。它是在1972年由魯道夫·貝爾發明的,他稱之為"對稱二叉B樹",它現代的名字是在 Leo J. Guibas 和 Robert Sedgewick 於1978年寫的一篇論文中獲得的。它是複雜的,但它的操作有著良好的最壞情況執行時間,並且在實踐中是高效的: 它可以在O(log n)時間內做查詢,插入和刪除,這裡的n是樹中元素的數目。

紅黑樹的性質

紅黑樹是每個節點都帶有顏色屬性的二叉查詢樹,顏色為紅色或黑色。在二叉查詢樹強制一般要求以外,對於任何有效的紅黑樹我們增加了如下的額外要求:
性質1. 節點是紅色或黑色。
性質2. 根是黑色。
性質3. 所有葉子都是黑色(葉子是NIL節點)。
性質4. 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
性質5. 從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點。

理解紅黑樹新增和刪除的關鍵點

1.左旋和右旋:
左旋和右旋有什麼好處呢?左旋和右旋並不改變紅黑樹的排序性質,即旋轉後它還是一棵二叉查詢樹!下面會提到旋轉帶來的另外的效果!

2.這裡我需要創造幾個概念:“黑深度”、紅黑樹“穩定”、“顏色左旋”、“顏色右旋”。
   a.黑深度”:就是指包括整棵樹(或子樹)的根節點在內,到葉子節點的最長路經中,黑節點個數。這個與黑高度有什麼不同呢?黑高度計算的時候是不包括這棵樹(或者子樹)的根節點的。而我下面的討論中是要把樹(或子樹)的根節點算上的,所以搞了“黑深度”這個概念。
    舉個例子:

    例如上圖,整棵樹的左子樹的“黑深度”為3(最長的路經為BEH或BEI,有3個黑節點),整棵樹的右子樹的“黑深度”為1(最長路經為CF或CG,有1個黑節點)
    b.紅黑樹“穩定”:是指整棵樹從根節點到葉子節點的所有路經中,黑節點個數都一樣。(也就是紅黑樹的性質5),例如上圖就是不穩定的樹,需要調整,上圖的左子樹是不“穩定”的,因為“黑深度”取值有2,3,3,兩種取值,上圖的右子樹是穩定的,每條路徑的“黑深度”都為1,所以是“穩定”的。
    c.顏色的左旋和右旋      //看個圖就明白了:
   
   顏色的右旋就是上圖的逆過程。

    好吧,這幾個是我自己用到的概念,下面舉個簡單的例子來說明這幾個概念的用武之地。

        總結一下這兩步操作帶來的效果:
        通過節點的左旋和黑顏色的左旋,可以使左子樹的“黑深度”+1,右子樹“黑深度”-1,並且整棵樹的“黑深度”跟原來的一樣!(這個配合紅節點可以染黑,能使得整棵樹又恢復穩定!後面會講到)
        通過節點左旋的和紅顏色的左旋,不會改變原來整棵樹的“穩定”狀態,只會改變紅節點的位置。)即原來是“穩定的”,操作後還是“穩定”的,如果原來是不“穩定”的,操作後也是不“穩定的”。(這個可以用來調整把紅節點調整到其它地方而不改變原來整棵樹的性質!)
  

        調整紅黑樹的思想1:儘量區域性調整,保持區域性樹的“穩定”而不去破壞樹的其它部分!

        下面舉另外一個例子來引出調整紅黑樹的另一個思想:旋轉時利用可染黑的紅節點!
         上面提到,節點左旋和顏色左旋帶來的效果,那就有個問題了,如果原來左右子樹的“黑深度”差值為1,那麼旋轉完差值還是1啊,並沒有變成穩定的。這個怎麼破?利用紅節點!直接看圖:
        

        從上圖中可以看到,雖然旋轉後左右子樹的“黑深度”差1,但是,右子樹有可以拿來利用的紅節點!紅節點染黑之後,左右子樹的黑深度就相同了!也就是穩定了!

        調整紅黑樹的思想2:利用可以染黑的紅節點!

        有了上面兩種簡單的思想,下面理解紅黑樹的新增和刪除操作的各種情況應該非常簡單了。

紅黑樹的插入

        從總體上先分析一下:
        新增的節點可以為紅色,也可以為黑色,選哪種好呢?選黑色的話,意味著整棵樹的黑高度+1,違反了性質5,那麼要調整整棵樹的其它很多路徑,顯然特別麻煩。選紅色呢?如果該節點的父節點為紅色,那就違反了性質4.《演算法導論》裡面的演算法採用的是插入的節點選為紅色,為什麼不選黑色呢?選黑色不行嗎?下面說明,選黑色也是可以的,但是情況最終會轉換為選紅的一樣!如下圖所示:
        

下面開始正式討論紅黑樹的插入的幾種情況:
    a.插入後該節點為根節點,很顯然,染黑即可...
    b.插入後,該節點的父節點為黑色,很顯然,不用任何處理...
    我們要討論的是插入後,父節點為紅色的情況:
    1.叔叔節點為紅色





        從上面的圖可以看到,情況1的最終流向,要麼是結束,要麼就是最終轉換為情況2。

        2.叔叔節點為黑色
        前面討論過,通過節點的旋轉和紅顏色的旋轉,是能改變紅節點的位置,並且保持原來樹的穩定性的!
        



紅黑樹的刪除

        紅黑樹節點的刪除只是比紅黑樹節點的新增稍微複雜一點點而已!
        首先應先去複習複習二叉查詢樹的刪除操作。分幾種情況:
        1.欲刪除的節點最多隻有一個孩子節點,則直接刪除該節點,並把孩子節點或者空與該節點的父節點相連。
        2.欲刪除的節點有兩個孩子,則使用它的後繼(或者前驅)節點覆蓋該節點,然後刪除其後繼(或者前驅)節點,而由於其後繼(或者前驅)節點最多隻有一個孩子節點,所以刪除操作最多隻傳遞一次。這裡注意,使用前驅或者後繼節點去覆蓋原節點都是一樣的,網上有的版本是使用前驅節點去覆蓋,《演算法導論》使用的是用後繼節點去覆蓋的,這裡選擇用後繼節點去覆蓋。

         好了,從上面的分析中,可以知道刪除的時候,最終真正刪除的節點是最多隻有一個孩子的節點。我們先把簡單的情況幹掉,再來討論複雜的情況:
a.欲刪除的節點是紅色的,顯然,把該節點直接刪掉,把孩子節點或者NIL節點接回去就ok了,什麼性質都沒有違反。
b.欲刪除的節點是黑色的,那麼刪掉它的話,顯然某些路徑會少一個黑節點,性質5被破壞。這個有什麼好辦法解決呢?
掐腳趾頭想想就知道了:
1.既然我某棵子樹的黑高度少1,把整棵樹其它跟我不一樣黑高度的,都通通給我減1。這個辦法好,簡單粗暴。
2.上面的辦法1有些粗暴,要是讓所有其它子樹都減1有點困難的話呢...那就想辦法把我自己這顆子樹的黑高度+1不就完了,等等,怎麼加呢?用紅節點變黑節點!
        下面開始討論。既然要討論,那就要分各種情況討論,那麼根據什麼去分情況呢?根據父節點?還是其它節點?顯然從總體上看,我們刪除了一個節點,那肯定是在區域性的一棵樹中(這個最小的區域性的樹的樹根就是當前節點的父節點。),左右子樹的”黑深度“相差1。這個要怎麼辦呢?用之前討論過的,旋轉根節點並旋轉顏色來解決。我們首先根據兄弟的顏色來劃分情況。
         注意,下面的“當前節點“是指刪除該節點後接回去的那個節點(也就是刪除的節點的唯一孩子節點)。下圖中的白色節點代表任意顏色。並且當前節點與兄弟節點的左右關係式對稱的,下面以當前節點在左邊,兄弟節點在右邊為例討論。

情況1:兄弟節點為紅色,那麼父節點也就肯定是黑色了(性質4)


    從上圖可以知道,情況1其實是可以轉成情況2的。

情況2:兄弟節點為黑色。且左子樹比右子樹”黑深度“少1。這個算算還可以分幾種情況,根據兄弟節點的兩孩子顏色分,2*2=4有四種情況呢。
         先看兄弟節點左右孩子均為黑色的情況。




從上圖可以看出,情況2.1最終由兩種情況能結束,有一種情況是回到最初的問題。

情況3:兄弟節點為黑色,且兄弟節點的孩子至少有一個是紅色。


這種情況很好處理,利用之前分析過的,左旋+左旋黑色,可以使左子樹“黑深度”+1,右子樹“黑深度”-1,且整棵樹的穩定性不變,旋轉後,變為右子樹“黑深度”比左子樹少1,但是右子樹有個紅節點!利用可以染黑的紅節點,使左右子樹的“黑深度”變得一致!

還有兩種情況還沒分析:
1.兄弟節點為黑色,左孩子為紅色,右孩子為黑色;
2.兄弟節點為黑色,左右孩子均為紅色。
想一想就應該明白,右旋一下就能轉換為上面的情況了,因為只需要保證右邊能騰出一個紅節點拿來染黑就ok了!分析結束!

        
最後,歡迎轉載,歡迎拍磚。   






        



相關文章