紅黑樹 插入和刪除詳解 -- 草稿

 

- 這只是一篇草稿,還沒排版,以前一直想給本身寫一個文章的模板,一直還沒寫,因此估計寫的比較慢,因此先放出來文章,後面再來填坑吧。   這篇文章的初衷是看到面試問題有問到紅黑樹,甚至有手撕紅黑樹代碼的?可怕,紅黑樹的討論狀況不少,一不當心就繞進去,真讓人犯難,並且網上的文章各有各的說法,很多人理解甚至是錯誤的。java

 

-  紅黑樹:紅黑樹是一種二叉平衡樹,二叉查找樹,它牛逼之處就在於它足夠的平衡,能夠達到高度至多2lg(n+1),因此在java中的treemap和c++ set, multiset, map, multimap就使用的紅黑樹。 c++

- 紅黑樹的性質:1. 結點分爲紅色和黑色兩種  2.根節點是黑色的  3.每一個葉子結點(nil)是黑色的(就是空表明了黑色) 4.不存在父子都是紅色的狀況(連續兩個紅色) 5.任意孩子到根節點的路徑上的黑色數量都是相等的(important)面試

- 本文將解釋 如下內容:算法

 1.  紅黑樹的插入(簡單的插入)與調整(調整平衡)。 ide

2. 紅黑樹的刪除(也帶有調整,使其便於調整平衡)與調整(調整平衡)。spa

 

### 插入3d

#### 插入方式:code

  先考慮插入什麼顏色的結點,這裏須要說明顏色只是咱們用於保證平衡的一種方法,與數據無關。 由於插入黑色結點除非在根節點,不然必定會致使一個分支上的全部子分支都多了一個黑,如此不知足性質5(簡稱 路黑同)。因此咱們插入紅色。插入方式就是普通的二叉排序樹的插入方式。就這麼簡單。對象

 

#### 調整:blog

  由於插入的調整相對簡單,咱們能夠將全部的狀況列舉出來而後討論,列舉的依據是:由於插入紅色的結點不會對插入節點的子節點形成任何影響(即符合性質1-5,當我在作任何斷言時,最好本身試試),因此僅需考慮插入結點的祖先的狀況。

(圖實在太多了,因此暫時手畫了sorry) 

 

具體而言咱們要作的就是討論一段區間,它的全部狀況必須知足調整後對祖先和子孫都而言都仍然平衡,由於原來的兩段都是平衡的,因此只需保證在討論區間中是的任意左右子樹知足性質,而且不改變原來和祖先和子樹的接口,那麼就必定能維持性質不變。

 

 圖中從左到右,分別是紅色,黑色,任意顏色,附加黑(意思是除了本來結點帶有的顏色還附加一層黑色,用處後面用到的時候再講),

圖中沒有畫出兒子結點不表明它兒子就是nil,若是有葉子結點會標nil的。

 下圖爲全部狀況

 

狀況1:單個結點,沒有父節點。

 

 

 

調整方法:紅色變成黑色,性質2(簡稱 根黑),

狀況2:  父節點是黑色,這樣原本就是平衡的,可是因爲討論的結點兒子多是紅色,能夠將這種狀況轉化成討論它的兒子的狀況,它的兒子的父節點是紅色,對應接下來正好要討論的狀況3,因此狀況2應該算狀況3的一種子狀況。

 

狀況3:父節點是紅色

分爲兩種狀況, 一個是父的右孩子,一個是父的左孩子。

 

 

 

 

    

 

對於狀況3.1.1  只需將父節點和叔父結點變成黑色,而後祖父結點變成紅色,就能夠維持兩個分支的黑色結點一致,而且沒有兩個紅色,能夠對照知足了全部性質,這就算是恢復好了,但僅僅是針對圖中最上方的結點的子樹,若以調整後最上方的紅色結點做爲討論對象,討論他的父結點狀況,就又能夠遞歸的分類討論進行恢復,實際就是算法中的遞歸調用。

 

對於狀況3.1.2 方式相似3.1.1

 

對於狀況3.1.3, 能夠先以紅-紅中的父節點進行左旋轉,而後和狀況3.2.3同樣了,只需再以它爲中心右旋一次,並如圖變換顏色便可,最重要的依據就是分支的黑色要一致。因此調整後整棵樹就恢復好了。由於討論區間分別和子樹以及子樹鏈接的部分的紅色維持不變,因此不會影響到本來的祖先和子孫的平衡狀況。

 

 

 

對於其餘狀況,能夠相似上面的四種狀況進行操做,再也不一一介紹,爲了和教科書上(算法導論)保持一致,而且簡化狀況,咱們須要將多種狀況進行合併。合併的最後結果只剩下三種狀況。如圖中棕色的標號, (要記就記如下的結果)

①表明了第一種狀況,這裏把只有一個紅色結點的狀況歸類爲了父節點爲黑色,實際上也是這麼定義的,根節點的父節點爲nil,nil爲黑色,這樣就進行了統一。

①概括了狀況:插入節點的父和叔節點是紅色時,把他們變黑,再把爺爺變紅,遞歸調用爺爺做爲參數繼續算法,

 

 

②概括了狀況:插入節點的父是紅色uncle是黑色時,若是本身是父的右結點,先左旋到狀況③

 

③概括了狀況:本身是父的左結點)而後再把fauncle變成黑色,grandparent變成紅色再以父親爲參數右旋

 

 

 

 對應的還有①', ②',③',它和①, ②,③的區別就在於討論的結點是在左子樹仍是右子樹,操做方法是對稱的,因此恢復算法一共有六種狀況,可是三種也對。

 附上代碼:

#插入方法
RB-INSERT(T,z): #z就是插入的結點也是fix時當前討論的結點
    y = T.nil
    x = T.root
    while x != T.nil
        y = x
        if z.key < x.key
            x = x.left
        else x = x.right
    z.p = y
    if y == T.nil
        T.root = z
    else if z.key  < y.key
        y.left = z
    else y.right = z
    z.left  = T.nil
    z.right = T.nil
    z.color = RED
    RB-INSERT-FIXUP(T,z)
View Code

 

 

View Code

 

---

 

### 刪除:

刪除狀況討論若是按照以前的將全部狀況都討論出來,那會很累,而且會繞進去, 因此咱們仍是根據現有的算法,來討論分類

##### 刪除的方式:根據狀況刪除時就作一些調整。

##### 主要的討論分類方式:將刪除分類爲1.若是刪除的結點只有一個孩子(不考慮nil),又可繼續討論下去,那個孩子是左孩子仍是右孩子,

2.若是刪除的結點有兩個孩子,一樣也可詳細討論  ,固然你也能夠說還有一種狀況,若是刪除的結點一個孩子都沒有,那種狀況只用單純的刪除,刪除過程當中沒有進行額外的操做,全部刪除操做都要進行刪除步驟,因此這裏不進行討論,注意到咱們僅討論了刪除節點爲根的子樹狀況,而沒有討論它的祖先結點,不是由於不影響,而是在刪除過程當中僅須要對以上幾種狀況分開討論以進行額外的操做,便於刪除恢復。 

##### 刪除的具體狀況: 刪除過程咱們的主要目的是保證經過一些方法讓整棵樹仍然保持平衡(加附加黑的方式),而後在恢復的過程當中解決附加黑的問題,如下討論中a爲待刪除結點

刪除掉a,將另外一個結點做爲替換,由於 原來的a結點可能就是黑色的,這樣a的子樹就少了一個黑色,因此將b塗成黑色。咱們發現這種狀況就再也不須要調整了。已經知足了要求的各類性質,不信能夠試試。

 

若是有兩個孩子的話,而且 a(待刪除結點)的兒子結點的左兒子爲nil,就說明a是c最小的後繼,刪除a後,將c移動到a的位置,固然也能夠用a的左子樹中最大的數即a的前驅來替換他。除此以外,這種狀況下,c必須和a的顏色相同,目的是使得改動最小,可是當a是黑色的時候,因爲c,d根據性質必有一黑,當c是黑時,c替換後,c的分支相對於原來,少了一個黑色的結點,這時候就須要多加一重黑色來保證平衡

 

 

若是有兩個孩子,而且a的兒子結點的兒子結點不爲空,那麼,經過找到左兒子的最小值,即dfs一直找最左兒子爲nil的d,將它和a進行交換,而後e代替nil,同理仍是d代替a的顏色,這時a爲黑,且d爲黑時就會不平衡,這時候如同狀況2也須要給e多加一層黑色。

 

算法中①的狀況由於左爲nil和右爲nil操做雖然對稱,可是不同,因此通常分紅四種狀況

 

 

#####  刪除恢復,

最激動人心的時刻,刪除恢復,不知是誰想的附加黑色這種騷操做,通過了上面的刪除,咱們如今只須要考慮恢復的狀況只有如何將附加黑狀況進行處理,基本思路是隻考慮附加黑結點的子樹之外的結點,由於算法試圖將附加黑不斷向樹根傳遞,來達到在某一部消除的方式來恢復。

具體的討論狀況分爲 附加黑結點的兄弟是1.紅,2.黑(有分爲三種狀況: 1。兄弟的兒子爲全黑 2.左紅右黑 3.左any右紅 )本身畫畫是否討論徹底了。 這樣算一共4種子狀況以下: 

狀況1:

這種狀況是 附加黑的兄弟是紅色,進行左旋,對應了第二大類的討論。

 

狀況2:

兄弟爲黑,而且兄弟兒子全黑,這時候將附加黑向上傳遞,這樣右邊就多了一重黑色,因此將c改爲紅色,b的子樹就知足要求了,而後繼續以b爲結點進行遞歸

 

兄弟爲黑而且左紅右黑,經過以c爲中心右旋再交換dc顏色就達到了狀況4

狀況4: 

 

 

經過以b爲中心左旋,這時咱們發現左邊和右邊肯定的黑分別爲2和1,原來是1和1,因此須要給右邊增長一層黑,經過將附加黑給b讓b稱爲黑色,同時使得e爲黑色,這樣就知足了左2黑右2黑,可是要保持原來的黑色數爲1,因此就將c改爲原來b位置應有的任意

 

 

- 以上四種狀況只考慮了a(帶附加黑的點)是左兒子的狀況, 一樣右兒子是對稱,因此和插入同樣,4種基本狀況實際是2x4種詳細狀況

 

如下爲刪除的僞代碼:

RB-DELETE(T,z):
    y = z
    y-origin-color = y.color
    if z.left == T.nil
        x = z.right
        RB-TRANSPLANT(T, z, z.right)
    else if z.right ==T.nil
        x = z.left
        RB-TRANSPLANT(T,z,z.left)
    else  y = TREE-MINIMUN(z,right)
        y-original-color = y.color
        x = y.right
        if y.p == z
            x.p = y
        else RB-TRANPLANT(T,y,y.right)
            y.right = z.right   
            y.right.p = y
        RB-TRANSPLANT(T,z,y)
        y.left = z.left
        y.left.p = y
        y.color = z.color
        if y-original-color ==BLACK
            RB-DELETE-FIXUP(T,x)
View Code
 

 

RB-DELETE-FIXUP(T,x)
    while x!=T.root and x.color ==BLACK:
        if x== x.p.left
            w = x.p.right
            if w.color ==RED                                                     # case 1
                w.color = BLACK
                x.p.color = RED
                LEFT-ROTATE(T,x.p)
                w  = x.p.right
            if w.left.color ==BLACK and w.right.color ==BLACK 
                w.color = RED                                                    #case 2
                x = x.p
            else if w.right.color ==BLACK
                w.left.color =  BLACK                                         #case 3
                w.color = RED
                RIGHT-ROTATE(T,w)
                w = x.p.right
            w.color = x.p.color                                                 #case 4
            x.p.color = BLACK
            w.right.color = BLACK
            LEFT-ROTATE(T, x.p) 
            x = T.root         
        else  (same as then clause with "right" and left exchanged )   #symetric case
    x.color = BLACK                
View Code
相關文章
相關標籤/搜索