徹底搞懂紅黑樹(三)

從紅黑樹上刪除一個節點,可以先用普通二叉搜索樹的方法,將節點從紅黑樹上刪除掉,然後再將被破壞的紅黑性質進行恢復。

      我們回憶一下普通二叉樹的節點刪除方法:Z指向需要刪除的節點,Y指向實質結構上被刪除的結點,如果Z節點只有一個子節點或沒有子節點,那麼Y就是指向Z指向的節點。如果Z節點有兩個子節點,那麼Y指向Z節點的後繼節點(其實前趨也是一樣的),而Z的後繼節點絕對不可能有左子樹。因此,僅從結構來看,二叉樹上實質被刪除的節點最多隻可能有一個子樹。
現在我們來看紅黑性質的恢復過程:
      ①如果Y指向的節點是個紅色節點,那麼直接刪除掉Y以後,紅黑性質不會被破壞。操作結束。
      ②如果Y指向的節點是個黑色節點,那麼就有幾條紅黑性質可能受到破壞了。首先是包含Y節點的所有路徑,黑高度都減少了一(第5條被破壞)。其次,如果Y的有紅色子節點,Y又有紅色的父節點,那麼Y被刪除後,就出現了兩個相鄰的紅色節點(第4條被破壞)。最後,如果Y指向的是根節點,而Y的子節點又是紅色的,那麼Y被刪除後,根節點就變成紅色的了(第2條被破壞)。
      其中,第5條被破壞是讓我們比較難受的。因爲這影響到了全局。這樣動作就太大太複雜了。而且在這個條件下,進行其它紅黑性質的恢復也很困難。所以我們首先解決這個問題:如果不改變含Y路徑的黑高度,那麼樹的其它部分的黑高度就必須做出相應的變化來適應它。所以,我們想辦法恢復原來含Y節點的路徑的黑高度。做法就是:無條件的把Y節點的黑色,推到它的子節點X上去。(X可能是NIL節點)。這樣,X就可能具有雙重黑色,或同時具有紅黑兩色,也就是第1條性質被破壞了。(這個很重要)

      但第1條性質是比較容易恢復的:

一、如果X是同時具有紅黑兩色,那麼好辦,直接把X塗成黑色,就行了。而且這樣把所有問題都解決了。因爲將X變爲黑色,2、4兩條如果有問題的話也會得到恢復,算法結束。

二、如果X是雙黑色,那麼我們希望把這種情況向上推一直推到根節點(調整樹結構和顏色,X的指向新的雙黑色節點,X不斷向上移動),讓根節點具雙黑色,這時,直接把X的一層黑色去掉就行了(因爲根節點被包含在所有的路徑上,所以這樣做所有路徑同時黑高減少一,不會破壞紅黑特徵)。(雙黑調節就是按照這種思路:無限把雙黑節點上推,直到出現第四種情況)

      下面就具體地分析如何恢復1、2、4三個可能被破壞的紅黑特性:我們知道,如果X指向的節點是有紅黑兩色,或是X是根節點時,只需要簡單的對X進行一些改變就行了。要對除X節點外的其它節點進行操作時,必定是這樣的情況:X節點是雙層黑色,且X有父節點P。由知可知,X必然有兄弟節點W,而且這個W節點必定有兩個子節點。(因爲這是原樹滿足紅黑條件要求而自然具備的。X爲雙黑色,那麼P的另一個子節點以下一定要有至少兩層的節點,否則黑色高度不可能和X路徑一致。也就是說至少兩層黑。其實可以說,這就是一個遞歸函數的遞歸公式,只不過情況較多而已。可以參看july的最長子字符串序列,和這種情況類似)。所以我們就分析這些節點之間如何變形,把問題限制在比較小的範圍內解決。另一個前提是:X在一開始,肯定是樹底的葉節點或是NIL節點,所以在遞歸向上的過程中,每一步都保證下一步進行時,至少 X的子樹是滿足紅黑特性的。因此子樹的情況就可以認爲是已經正確的了,這樣,分析就只限制在X節點,X的父節點P和X的兄弟節點W,以及W的兩個子節點。這些個節點中。
      下面僅僅考慮X原本是黑色的情況即可。
      在這種情況下,X此時應該具有雙重黑色,算法的過程就是將這多出的一重黑色向上移動,直到遇到紅節點或者根節點。
      接着往下分析, 會遇到4種情況,實際上是8種, 因爲其中4種是相互對稱的,這可以通過判斷X是其父節點的右孩子還是左孩子來區分。下面我們以X是其父節點的左孩子的情況來分析這4種情況,實際上接下來的調整過程,就是要想方設法將經過X的所有路徑上的黑色節點個數增加1。
      具體分爲以下四種情況:(下面針對x是左兒子的情況討論,右兒子對稱)
      Case1:X的兄弟W是紅色(想辦法將其變爲黑色)
       由於W是紅色的,因此其兒子節點和父節點必爲黑色,只要將W和其父節點的顏色對換,在對父節點進行一次左旋轉,便將W的左子節點放到了X的兄弟節點上,X的兄弟節點變成了黑色,且紅黑性質不變。但還不算完,只是暫時將情況1轉變成了下面的情況2或3或4。(本來x 爲雙黑,只要把A再塗成雙黑就完事了,和我們之前說過的要把雙黑往上推一樣,但那樣對問題根本沒一點影響,還不如不做。其實旋轉是可以想象到的,左邊x的刪除了一個黑節點,那必然導致右邊重了,要左旋轉)

圖一

    Case2:X的兄弟節點W是黑色的,而且W的兩個子節點都是黑色的。此時可以將X的一重黑色和W的黑色同時去掉,而轉加給他們的父節點上,這是X就指向它的父節點了,因此此時父節點具有雙重顏色了。這一重黑色節點上移。(因爲A下面是平衡的,所以不用改動,但x要把一層黑給頂上去,那w這邊就相當於多了一層黑,所以把w的黑給去掉,成爲紅色。至於A怎麼辦,那要看A本身的顏色是什麼了。如下:)

圖二

      如果父節點原來是紅色的,現在又加一層黑色,那麼X現在指向的這個節點就是紅黑兩色的,直接把X(也就是父節點)着爲黑色。問題就已經完整解決了。
     如果父節點現在是雙層黑色,那就以父節點爲新的X進行向上的下一次的遞歸。
    Case3:X的兄弟節點W是黑色的,而且W的左子節點是紅色的,右子節點是黑色的。此時通過交換W和其左子節點的顏色並進行一次向右旋轉就可轉換成下面的第四種情況。注意,原來L是紅色的,所以L的子節點一定是黑色的,所以旋轉中L節點的一個子樹掛到之後着爲紅色的W節點上不會破壞紅黑性質。變形後黑色高度不變。(L旋轉後,左子樹的性質是不會變的,但右子樹多了個黑的w 就有問題了,所以要把w改爲紅色。其實case3是把問題轉換爲case4.)

圖三

    Case4:X的兄弟節點W是黑色的,而且W的右子節點是紅色的。這種情況下,做一次左旋,W就處於根的位置,將W保持爲原來的根的位置的顏色,同時將W的兩個新的兒子節點的顏色變爲黑色,去掉X的一重黑色。這樣整個問題也就得到了解決。遞歸結束。(在代碼上,爲了標識遞歸結束,我們把X指向根節點) (其實是相當於 x這邊多了一層黑,怎麼辦呢,就把A點給按下去,這樣x兩層黑就變爲 x本身黑+A的黑。A是下去了,那自然要把w給提上來,w提上來之後L就變到左邊去了。L子樹保持不變,原因很簡單,都是兩層黑+L本身。但w提上來了,可這邊少了一層黑啊,所以直接把R變爲黑,ok 問題解決了。)

圖四

      因此,只要按上面四種情況一直遞歸處理下去,X最終總會指向根結點或一個紅色結點,這時我們就可以結束遞歸併把問題解決了。
      以上就是紅黑樹的節點刪除全過程。
      總結:
      如果我們通過上面的情況畫出所有的分支圖,我們可以得出如下結論
      插入操作:解決的是 紅-紅 問題
      刪除操作:解決的是 黑-黑 問題

      即你可以從分支圖中看出,需要往上遍歷的情況爲紅紅(插入),或者爲黑黑黑(刪除)的情況,如果你認真分析並總結所有的情況後,並堅持下來,紅黑樹也就沒有想象中的那麼恐怖了,並且很美妙;