紅黑樹詳解


在介紹紅黑樹之前,有必要對樹的概念以及相關理論作一個概述:

1. 樹的導覽

樹由節點(Nodes)和 邊(edges)構成。樹有根節點(root),邊(deges),父節點(parent),子節點(child),葉節點(leaf)。如果最多只允許兩個子節點,即所謂的二叉樹(binary tree)。不同的節點如果擁有相同的父節點,則彼此互為兄弟節點(siblings)。根節點至任何節點之間有唯一路徑(path),路徑所經過的邊數,成為路徑長度(length)。根節點至任一節點的路徑長度,即所謂該節點的深度(depth)。根節點的深度永遠是0。 某節點至其最深子節點(葉節點)的路徑長度,成為該節點的高度(height)。整棵樹的高度,便以根節點的高度來代表。節點A->B 之間如果存在(唯一)一條路徑,那么A 稱為B的祖代(ancestor),B稱為A的子代(descendant)。任何節點的大小(size)是指其所有子代(包括自己)的節點總數。

1.1 二叉搜索樹(binary search tree)

所謂二叉樹(binary tree),其意義是:“任何節點最多只允許兩個子節點”。稱為左子節點和右子節點。二叉搜索樹可提供對數時間的元素插入和訪問。二叉搜索樹的節點放置規則是:任何節點的鍵值一定大於其左子樹中的每一個節點的鍵值,並小於其右子樹中每一個節點的鍵值。因此,從根節點一直往左走,直至無左路可走,即得最小元素;從根節點一直往右走,直至無右路可走,即得最大元素。

1.2 平衡二叉搜索樹(balanced binary search tree)

也許因為輸入值不夠隨機,也許因為經過某些插入或刪除操作,二叉搜索樹可能會失去平衡,造成搜尋效率低落的情況。

所謂樹形平衡與否,並沒有一個絕對的測量標准。“平衡”的大致意義是:沒有任何一個節點過深(深度過大)。不同的平衡條件,造就出不同的效率表現,以及不同的實現復雜度。有數種特殊結構如AVL-tree, RB-tree、AA-tree ,均可實現出平衡二叉搜索樹。

1.3 AVL tree (Adelson-Velskii-Landis tree)

AVL tree 是一個“加上了額外平衡條件”的二叉搜索樹。其平衡條件的建立是為了確保整棵樹的深度為O(logN)。直觀上的最佳平衡條件是每個節點的左右子樹有着相同的高度,但這未免太過苛刻,我們很難插入新元素而又保持這樣的平衡條件。AVL tree 於是退而求其次,要求任何節點的左右子樹高度相差最多1。這是一個較弱的條件,但仍能夠保證“對數深度”平衡狀態。

只要調整“插入點之根節點”路徑上,平衡狀態被破壞之個節點中最深的那一個,便可使整棵樹重新獲得平衡。假設該最深節點為X,由於節點最多擁有兩個子節點,而所謂“平衡被破壞”意味着X的左右兩棵子樹的高度相差2,因此我們可以輕易將情況分為四種:

(1)插入點位於X的左子節點的左子樹——左左;

(2)插入點位於X的左子節點的右子樹——左右;

(3)插入點位於X的右子節點的左子樹——右左 ;

(4)插入點位於X的右子節點的右子樹——右右 。

情況1、4彼此對稱,成為外側插入,可采用單旋轉操作調整解決。

情況2、3彼此對稱,成為內側插入,可采用雙旋轉調整解決。

 

 

一、紅黑樹的介紹

先來看下算法導論對R-B Tree的介紹: 紅黑樹,一種二叉查找樹,但在每個結點上增加一個存儲位表示結點的顏色,可以是Red或Black。 通過對任何一條從根到葉子的路徑上各個結點着色方式的限制,紅黑樹確保沒有一條路徑會比其他路徑長出倆倍,因而是接近平衡的。

紅黑樹,作為一棵二叉查找樹,滿足二叉查找樹的一般性質。下面,來了解下 二叉查找樹的一般性質。

二叉查找樹

二叉查找樹,也稱有序二叉樹(ordered binary tree),或已排序二叉樹(sorted binary tree),是指一棵空樹或者具有下列性質的二叉樹:

若任意節點的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 若任意節點的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值; 任意節點的左、右子樹也分別為二叉查找樹。 沒有鍵值相等的節點(no duplicate nodes)。  因為一棵由n個結點隨機構造的二叉查找樹的高度為lgn,所以順理成章,二叉查找樹的一般操作的執行時間為O(lgn)。但二叉查找樹若退化成了一棵具有n個結點的線性鏈后,則這些操作最壞情況運行時間為O(n)。

紅黑樹雖然本質上是一棵二叉查找樹,但它在二叉查找樹的基礎上增加了着色和相關的性質使得紅黑樹相對平衡,從而保證了紅黑樹的查找、插入、刪除的時間復雜度最壞為O(log n)。

但它是如何保證一棵n個結點的紅黑樹的高度始終保持在logn的呢?這就引出了紅黑樹的5個性質

(1)每個結點要么是紅的要么是黑的。

(2)根結點是黑的。 

(3)每個葉結點(葉結點即指樹尾端NIL指針或NULL結點)都是黑的。 

(4)如果一個結點是紅的,那么它的兩個兒子都是黑的。 

(5)對於任意結點而言,其到葉結點樹尾端NIL指針的每條路徑都包含相同數目的黑結點。

正是紅黑樹的這5條性質,使一棵n個結點的紅黑樹始終保持了logn的高度,從而也就解釋了上面所說的“紅黑樹的查找、插入、刪除的時間復雜度最壞為O(log n)”這一結論成立的原因。

(注:上述第3、5點性質中所說的NULL結點,包括wikipedia.算法導論上所認為的葉子結點即為樹尾端的NIL指針,或者說NULL結點。然百度百科以及網上一些其它博文直接說的葉結點,則易引起誤會,因此葉結點非子結點)

如下圖所示,即是一顆紅黑樹(下圖引自wikipedia:http://t.cn/hgvH1l):

此圖忽略了葉子和根部的父結點。同時,上文中我們所說的 "葉結點" 或"NULL結點",如上圖所示,它不包含數據而只充當樹在此結束的指示,這些節點在繪圖中經常被省略,望看到此文后的讀者朋友注意。

二、樹的旋轉知識

當在對紅黑樹進行插入和刪除等操作時,對樹做了修改可能會破壞紅黑樹的性質。為了繼續保持紅黑樹的性質,可以通過對結點進行重新着色,以及對樹進行相關的旋轉操作,即通過修改樹中某些結點的顏色及指針結構,來達到對紅黑樹進行插入或刪除結點等操作后繼續保持它的性質或平衡的目的。

樹的旋轉分為左旋和右旋,下面借助圖來介紹一下左旋和右旋這兩種操作。

1.左旋

如上圖所示,當在某個結點pivot上,做左旋操作時,我們假設它的右孩子y不是NIL[T],pivot可以為任何不是NIL[T]的左子結點。左旋以pivot到Y之間的鏈為“支軸”進行,它使Y成為該子樹的新根,而Y的左孩子b則成為pivot的右孩子。

//偽代碼1:紅黑樹左旋(右旋類似) - LeftRoate(T, x)   
// ---------------------------------------------------------------------------------------------------------------------
y ← x.right                    //y是x的右孩子  
x.right ← y.left                //y的左孩子成為x的右孩子  
if y.left ≠ T.nil  
    y.left.p ← x      
y.p ← x.p                      //y成為x的父親  
if x.p = T.nil  
    then T.root ← y  
else if x = x.p.left  
    then x.p.left ← y  
else x.p.right ← y   
y.left ← x                       //x作為y的左孩子  
x.p ← y  

2.右旋
右旋與左旋差不多,再此不做詳細介紹。

樹在經過左旋右旋之后,樹的搜索性質保持不變,但樹的紅黑性質則被破壞了,所以,紅黑樹插入和刪除數據后,需要利用旋轉與顏色重塗來重新恢復樹的紅黑性質。

至於有些書如《STL源碼剖析》有對雙旋的描述,其實雙旋只是單旋的兩次應用,並無新的內容,因此這里就不再介紹了,而且左右旋也是相互對稱的,只要理解其中一種旋轉就可以了。

附錄4:SGI STL RB_Tree左旋和右旋實現

三、紅黑樹的插入

二叉查找樹的插入

要真正理解紅黑樹的插入,還得先理解二叉查找樹的插入。磨刀不誤砍柴工,咱們再來了解一下二叉查找樹的插入和紅黑樹的插入。

如果要在二叉查找樹中插入一個結點,首先要查找到結點要插入的位置,然后進行插入。假設插入的結點為z的話,插入的偽代碼如下:

//偽代碼2:二叉樹插入 - TREE-INSERT(T, z)
// ---------------------------------------------------------------------------------------------------------------------  
y ← NIL  
x ← T.root  
while x ≠ NIL  
    do y ←  x  
    if z.key < x.key        // 如果z的key值小,往Tree的左邊走,直到葉節點
        then x ← x.left            
    else x ← x.right  
z.p ← y  
if y == NIL  //空樹 
then T.root ← z
else if z.key < y.key then y.left ← z else y.right ← z

紅黑樹的插入和插入修復
現在我們了解了二叉查找樹的插入,接下來,咱們便來具體了解下紅黑樹的插入操作。紅黑樹的插入相當於在二叉查找樹插入的基礎上,為了重新恢復平衡,繼續做了插入修復操作。

假設插入的結點為z,紅黑樹的插入偽代碼具體如下所示:

//偽代碼4:紅黑樹插入和修復 - RB-INSERT(T, z)
// ---------------------------------------------------------------------------------------------------------------------  
y ← nil  
x ← T.root  
while x ≠ T.nil  
    do y ← x  
    if z.key < x.key  
        then x ← x.left  
    else x ← x.right  
z.p ← y  
if y == nil[T]  
    then T.root ← z  
else if z.key < y.key  
    then y.left ← z  
else y.right ← z  
z.left ← T.nil  
z.right ← T.nil  
z.color ← RED        // 插入節點默認為紅
RB-INSERT-FIXUP(T, z)        // 插入之后修復

把上面這段紅黑樹的插入代碼,跟之前看到的二叉查找樹的插入代碼比較一下可以看出,RB-INSERT(T, z)前面的第1~13行代碼基本上就是二叉查找樹的插入代碼,然后第14~16行代碼把z的左孩子和右孩子都賦為葉結點nil,再把z結點着為紅色,最后為保證紅黑性質在插入操作后依然保持,調用一個輔助程序RB-INSERT-FIXUP來對結點進行重新着色,並旋轉。

(1)如果插入的是根結點,由於原樹是空樹,此情況只會違反性質2,因此直接把此結點塗為黑色;  

(2)如果插入的結點的父結點是黑色,由於此不會違反性質2和性質4,紅黑樹沒有被破壞,所以此時什么也不做。

 但當遇到下述3種情況時又該如何調整呢?

(3)● 插入修復情況1:如果當前結點z的父結點是紅色且祖父結點的另一個子結點(叔叔結點)是紅色

(4)● 插入修復情況2:當前節點z的父節點是紅色,叔叔節點是黑色,當前節點是其父節點的右子

(5)● 插入修復情況3:當前節點z的父節點是紅色,叔叔節點是黑色,當前節點是其父節點的左子

答案就是根據紅黑樹插入代碼RB-INSERT(T, z)最后一行調用的RB-INSERT-FIXUP(T, z)函數所示的步驟進行操作,具體如下所示:

//偽代碼4:紅黑樹插入和修復 - RB-INSERT-FIXUP(T, z)  
// ---------------------------------------------------------------------------------------------------------------------
while z.p.color == RED  // 只有z的父節點為Red才做修復;當z的父節點為黑色時,屬於情況(2)
    do if z.p == z.p.p.left    // start_flag1,這里只考慮父節點為祖父節點的左孩子
        then y ← z.p.p.right        // y 指向當前節點z的祖父節點的另一個節點(叔叔節點)
        if y.color == RED            // 如果 叔叔節點 為 Red(執行if分支,結果:從修改情況1轉到修復情況2)
            then z.p.color ← BLACK          
            y.color ← BLACK                 
            z.p.p.color ← RED               
            z ← z.p.p                          
        else if z == z.p.right  // 當前節點z是其父節點的右子(執行if分支,左旋,結果:從插入修復情況2轉換成了插入修復情況3)
            then z ← z.p                     
            LEFT-ROTATE(T, z)            
        z.p.color ← BLACK                  
        z.p.p.color ← RED                   
        RIGHT-ROTATE(T, z.p.p)         
    else (same as then clause with "right" and "left" exchanged)   // // end_flag1,父節點為祖父節點的右孩子 情況類似
T.root.color ← BLACK  

下面,咱們來分別處理上述3種插入修復情況。

插入修復情況1:當前結點的父結點是紅色,祖父結點的另一個子結點(叔叔結點)是紅色。 如下偽代碼所示:

while z.p.color == RED  
    do if z.p == z.p.p.left  
        then y ← z.p.p.right  
        if y.color == RED  

此時父結點的父結點一定存在,否則插入前就已不是紅黑樹(因為,如果z的父節點的父節點不存在,則z的父節點為Root,而z的父節點又為Red,違反性質2.)。與此同時,又分為父結點是祖父結點的左孩子還是右孩子,根據對稱性,我們只要解開一個方向就可以了。這里只考慮父結點為祖父左孩子的情況,如下圖所示。

對此,我們的解決策略是:將當前節點的父節點和叔叔節點塗黑,祖父結點塗紅,把當前結點指向祖父節點,從新的當前節點重新開始算法。即如下代碼所示:

then z.p.color ← BLACK             
y.color ← BLACK                
z.p.p.color ← RED                
z ← z.p.p                          

所以,變化后如下圖所示:

於是,插入修復情況1轉換成了插入修復情況2

插入修復情況2:當前節點的父節點是紅色,叔叔節點是黑色,當前節點是其父節點的右子 此時,解決對策是:當前節點的父節點做為新的當前節點,以新當前節點為支點左旋。即如下代碼所示:

else if z == z.p.right  
    then z ← z.p                 
    LEFT-ROTATE(T, z)      

所以紅黑樹由之前的:

變化成:

從而插入修復情況2轉換成了插入修復情況3

插入修復情況3:當前節點的父節點是紅色,叔叔節點是黑色,當前節點是其父節點的左孩子  解決對策是:父節點變為黑色,祖父節點變為紅色,在祖父節點為支點右旋,操作代碼為:

z.p.color ← BLACK                 
z.p.p.color ← RED                 
RIGHT-ROTATE(T, z.p.p)     

最后,把根結點塗為黑色,整棵紅黑樹便重新恢復了平衡。所以紅黑樹由之前的:

變化成:

「回顧:經過上面情況3、情況4、情況5等3種插入修復情況的操作示意圖,讀者自會發現,后面的情況4、情況5都是針對情況3插入節點4以后,進行的一系列插入修復情況操作,不過,指向當前節點N指針一直在變化。所以,你可以想當然的認為:整個下來,情況3、4、5就是一個完整的插入修復情況的操作流程」

注:RB_Tree的插入,同時也完成了RB_Tree的構造。

附錄3:SGI STL RB_Tree插入修復實現

 

四、紅黑樹的刪除  

接下來,咱們最后來了解,紅黑樹的刪除操作。

 "我們刪除的節點的方法與常規二叉搜索樹中刪除節點的方法是一樣的,如果被刪除的節點不是有雙非空子女(1個或0個孩子),則直接刪除這個節點,用它的唯一子節點頂替它的位置,如果它的子節點是空節點,那就用空節點頂替它的位置,如果它的雙子全為非空,我們就把它的直接后繼節點(沿着左子樹不斷右走直至到達第一個右節點為nil的節點;或沿着右子樹不斷左走直至到達第一個左節點為nil的節點。)內容復制到它的位置,之后以同樣的方式刪除它的后繼節點,它的后繼節點不可能是雙子非空,因此此傳遞過程最多只進行一次。”

二叉查找樹的刪除

繼續講解之前,補充說明下二叉樹結點刪除的幾種情況,待刪除的節點按照兒子的個數可以分為三種:

(1)沒有兒子,即為葉結點。直接把父結點的對應兒子指針設為NULL,刪除兒子結點就OK了。

(2)只有一個兒子。那么把父結點的相應兒子指針指向兒子的獨生子,刪除兒子結點也OK了。

(3)有兩個兒子。這是最麻煩的情況,因為你刪除節點之后,還要保證滿足搜索二叉樹的結構。其實也比較容易,我們可以選擇左兒子中的最大元素或者右兒子中的最小元素放到待刪除節點的位置,就可以保證結構的不變。當然,你要記得調整子樹,畢竟又出現了節點刪除(當刪除的節點不是葉節點,需要調整其子樹)。習慣上大家選擇左兒子中的最大元素,其實選擇右兒子的最小元素也一樣,沒有任何差別,只是人們習慣從左向右。這里咱們也選擇左兒子的最大元素,將它放到待刪結點的位置。左兒子的最大元素其實很好找,只要順着左兒子不斷的去搜索右子樹就可以了,直到找到一個沒有右子樹的結點。那就是最大的了。 二叉查找樹的刪除代碼如下所示:

//偽代碼5:二叉樹刪除 - TREE-DELETE(T, z)  
// ---------------------------------------------------------------------------------------------------------------------
 1  if left[z] = NIL or right[z] = NIL  
 2      then y ← z      // z節點沒有兒子或者只有一個兒子,y指向z節點 3      else y ← TREE-SUCCESSOR(z)     // z節點有兩個兒子,y指向z的后繼節點(這里選擇左兒子的最大元素)  
 4  if left[y] ≠ NIL  
 5      then x ← left[y]  
 6      else x ← right[y]  
 7  if x ≠ NIL      // 如果y為葉節點(沒有兒子),或者y指向z的后繼節點並且此后繼節點沒有左兒子,則x為nil;否則(z節點有且只有一個兒子,或者z的后繼節點有左兒子),x不為nil.
 8      then p[x] ← p[y]   // x的父節點設為y的父節點(1.修改x節點的父指針,指向y的父節點)
 9  if p[y] = NIL  
10      then root[T] ← x  
11      else if y = left[p[y]]      // y 是其父節點的左子樹
12              then left[p[y]] ← x   // 重設指針,將y的父節點的左指針指向x(2.也即,令x節點為y的父節點的左兒子)
13              else right[p[y]] ← x  
14  if y ≠ z  // y指向z的后繼節點 15      then key[z] ← key[y]  // 把y的內容復制到被刪除節點z的位置,z的原來的內容被替換(也即刪除了z),而原來的y位置的節點頂替了z。
16           copy y's satellite data into z  
17  return y      // 返回(頂替z節點的)y節點的指針

紅黑樹的刪除和刪除修復

OK,回到紅黑樹上來,紅黑樹結點刪除的算法實現是:

RB-DELETE(T, z) 單純刪除結點的總操作:

//偽代碼6:紅黑樹刪除與修復 - RB-DELETE(T, Z)
// ---------------------------------------------------------------------------------------------------------------------
 1 if left[z] = nil[T] or right[z] = nil[T]    
 2    then y ← z    
 3    else y ← TREE-SUCCESSOR(z)    
 4 if left[y] ≠ nil[T]    
 5    then x ← left[y]    
 6    else x ← right[y]    
 7 p[x] ← p[y]    // 1. 紅黑樹,nil節點也要有parent指針指向一個節點,故不用判x是否為nil(nil,尾端節點,黑色)
 8 if p[y] = nil[T]    
 9    then root[T] ← x    
10    else if y = left[p[y]]    
11            then left[p[y]] ← x    
12            else right[p[y]] ← x    
13 if y ≠ z    
14    then key[z] ← key[y]    
15         copy y's satellite data into z    
//2. 以下為不同之處 ----------------------------------------------- 16 if color[y] = BLACK 17 then RB-DELETE-FIXUP(T, x) // 修復紅黑樹(根據頂替節點y(已用來頂替被刪除的節點z)的顏色,判斷是否需要修復工作.因為頂替節點在原來位置被刪除,修復工作從頂替節點的位置開始) 18 return y

參見附錄1:RB_Tree刪除節點解釋。

 

附錄1:RB_Tree刪除節點

相對於紅黑樹插入操作,刪除操作復雜的多。

第一:先看最簡單情況,即刪除紅色節點。刪除紅色節點,不影響紅黑樹平衡性質,如圖:

 

只需要刪除紅色節點,不需要進行調整,因為不影響紅黑樹的性質。  黑色節點沒有增多也沒有減少。

注意:以下幾種單支情況在平衡的紅黑樹中不可能出現。

因為上述的情況,紅黑樹處於不平衡狀態。(破壞性質“對於任意結點而言,其到葉結點樹尾端NIL指針的每條路徑都包含相同數目的黑結點”)

注:

a:(1)、(4)號圖中通過x左兒子到達nil的黑色節點數,和通過x-y在到達nil的黑色節點數不一樣;

b:(2)、(3)號圖中通過x右兒子到達nil的黑色節點數,和通過x-y在到達nil的黑色節點數不一樣;

所以,平衡狀態下紅黑樹要么單支黑-紅,要么有兩個子節點
 

第二:刪除單支黑節點

         

此種情況被包含在“第三”中,詳見“第三”分析

第三:若刪除節點有左右兩個兒子,即左右子樹,需要按照二叉搜索樹的刪除規律,從右子樹中找最小的替換刪除節點(該節點至多有一個右子樹,

無左子樹),我們將該節點記為y, 將刪除節點記為z,將y的右兒子記為x(可能為空)。

刪除規則:用y替換z(替換的結果是,y和z的顏色也會被替換),交換y與z顏色(交換y和z的顏色,其實也就是取消剛才的顏色替換,也即y和z位置原來的顏色不變),同時y = z,改變y的指向,讓y指向最終刪除節點。

為了便於理解,可以先這樣假設:將y與z的數據交換,但顏色不交換,這樣,實際相當於將刪除轉移到了y節點,而z處保持原先狀態(處於平衡)。此時可以完全不用理會z節點,直接刪除y節點即可。因為y最多只有一個右子樹,無左子樹,這便轉移到了“第二”。針對“第二”,在刪除y節點的時候,就要考慮到下面幾種情況:

  1. 若y為紅色,則這種情況如上述“第一”所述,並不影響平衡性。(null節點視為黑色);

  2. 若y為黑色,則刪除y后,x替換了y的位置,這樣x子樹相對於兄弟節點w為根的子樹少了一個黑節點,影響平衡,需要進行下面調整:

下面的調整工作(x已經替換到了y的位置)就是將x子樹中找一合適紅色節點,將其置黑,使得x子樹與w子樹達到平衡。

     a、若x為紅色,直接將x置為黑色,即可達到平衡;

    

      b、若x為黑色,則分下列幾種情況。

      

      情況1:x的兄弟w為紅色,則w的兒子必然全黑,w父親p也為黑。

      

       改變p與w的顏色,同時對p做一次左旋,這樣就將情況1轉變為情況2,3,4的一種。

 

      情況2:x的兄弟w為黑色,w的兩個兒子也都是黑的(否則,則相應進入了情況3、情況4),x與w的父親顏色可紅可黑。

       

       因為x子樹相對於其兄弟w子樹少一個黑色節點,可以將w置為紅色,這樣,x子樹與w子樹黑色節點一致,保持了平衡。

      new x為x與w的父親。new x相對於它的兄弟節點new w少一個黑色節點。如果new x為紅色,則將new x置為黑,則整棵樹平衡。否則,

      情況2轉換為情況1,3,4  情況2轉變為情況1,2,3,4.

 

      情況3:w為黑色,w左孩子紅色,右孩子黑色。

      

       交換w與左孩子的顏色,對w進行右旋。轉換為情況4

 

       情況4:w為黑色,右孩子為紅色。

       

        交換w與父親p顏色,同時對p做左旋。這樣左邊缺失的黑色就補回來了,同時,將w的右兒子置黑,這樣左右都達到平衡。

上述四種情況,每一次旋轉(左旋、右旋)都伴隨着顏色的交換。

個人認為這四種狀況比較難以理解,總結了一下:

  情況2:最好理解,減少右子樹的一個黑色節點,使x與w平衡,將不平衡點上移至x與w的父親。進行下一輪迭代。

  情況1:如果w為紅色,通過旋轉,轉成成情況2,3,4進行處理。而情況3轉換為情況4進行處理。也就是說,情況4是最接近最終解的情況。

  情況4:右兒子是紅色節點,那么將缺失的黑色交給右兒子,通過旋轉,達到平衡。

附錄2:SGI STL RB_Tree刪除實現:

// RB_Tree 刪除
inline _Rb_tree_node_base*
_Rb_tree_rebalance_for_erase(_Rb_tree_node_base* __z,
                             _Rb_tree_node_base*& __root,
                             _Rb_tree_node_base*& __leftmost,
                             _Rb_tree_node_base*& __rightmost)
{
  _Rb_tree_node_base* __y = __z;
  _Rb_tree_node_base* __x = 0;
  _Rb_tree_node_base* __x_parent = 0;
  if (__y->_M_left == 0)     // __z has at most one non-null child. y == z.
    __x = __y->_M_right;     // __x might be null.
  else
    if (__y->_M_right == 0)  // __z has exactly one non-null child. y == z.
      __x = __y->_M_left;    // __x is not null.
    else {                   // __z has two non-null children.  Set __y to
      __y = __y->_M_right;   //   __z's successor.  __x might be null.
      while (__y->_M_left != 0)
        __y = __y->_M_left;
      __x = __y->_M_right;
    }
  if (__y != __z) {          // relink y in place of z.  y is z's successor
    __z->_M_left->_M_parent = __y; 
    __y->_M_left = __z->_M_left;
    if (__y != __z->_M_right) {
      __x_parent = __y->_M_parent;
      if (__x) __x->_M_parent = __y->_M_parent;
      __y->_M_parent->_M_left = __x;      // __y must be a child of _M_left
      __y->_M_right = __z->_M_right;
      __z->_M_right->_M_parent = __y;
    }
    else
      __x_parent = __y;  
    if (__root == __z)
      __root = __y;
    else if (__z->_M_parent->_M_left == __z)
      __z->_M_parent->_M_left = __y;
    else 
      __z->_M_parent->_M_right = __y;
    __y->_M_parent = __z->_M_parent;
    __STD::swap(__y->_M_color, __z->_M_color);
    __y = __z;
    // __y now points to node to be actually deleted
  }
  else {                        // __y == __z
    __x_parent = __y->_M_parent;
    if (__x) __x->_M_parent = __y->_M_parent;   
    if (__root == __z)
      __root = __x;
    else 
      if (__z->_M_parent->_M_left == __z)
        __z->_M_parent->_M_left = __x;
      else
        __z->_M_parent->_M_right = __x;
    if (__leftmost == __z) 
      if (__z->_M_right == 0)        // __z->_M_left must be null also
        __leftmost = __z->_M_parent;
    // makes __leftmost == _M_header if __z == __root
      else
        __leftmost = _Rb_tree_node_base::_S_minimum(__x);
    if (__rightmost == __z)  
      if (__z->_M_left == 0)         // __z->_M_right must be null also
        __rightmost = __z->_M_parent;  
    // makes __rightmost == _M_header if __z == __root
      else                      // __x == __z->_M_left
        __rightmost = _Rb_tree_node_base::_S_maximum(__x);
  }
  /********* 以上完成二叉樹刪除操作,下面進行RB_Tree修復 *********/
  if (__y->_M_color != _S_rb_tree_red) {    // 只需考慮__y為黑色的情況(若__y為紅色,直接刪除,__x直接頂替__y原來的位置)
    while (__x != __root && (__x == 0 || __x->_M_color == _S_rb_tree_black))    //__x為nil(nil節點為黑色)
      if (__x == __x_parent->_M_left) {        // 只考慮__x是左兒子的情況(__x為右兒子情況類似)
        _Rb_tree_node_base* __w = __x_parent->_M_right;        // __x的兄弟節點w
        if (__w->_M_color == _S_rb_tree_red) {        // case1:兄弟節點w為紅色
          __w->_M_color = _S_rb_tree_black;
          __x_parent->_M_color = _S_rb_tree_red;
          _Rb_tree_rotate_left(__x_parent, __root);        // 左旋
          __w = __x_parent->_M_right;
        }
        if ((__w->_M_left == 0 || 
             __w->_M_left->_M_color == _S_rb_tree_black) &&
            (__w->_M_right == 0 || 
             __w->_M_right->_M_color == _S_rb_tree_black)) {    // case2:兄弟節點w為黑色,並且其兩個兒子都是黑色(或者都為nil)
          __w->_M_color = _S_rb_tree_red;
          __x = __x_parent;
          __x_parent = __x_parent->_M_parent;
        } else {        
          if (__w->_M_right == 0 || 
              __w->_M_right->_M_color == _S_rb_tree_black) {    // case3:兄弟節點w為黑色,並且其右兒子為黑(也即左兒子為紅)
            if (__w->_M_left) __w->_M_left->_M_color = _S_rb_tree_black;
            __w->_M_color = _S_rb_tree_red;
            _Rb_tree_rotate_right(__w, __root);
            __w = __x_parent->_M_right;
          }
          // case4:剩下最后一種情況,也即兄弟節點w為黑色,並且其右兒子為紅(左兒子可紅可黑)
          __w->_M_color = __x_parent->_M_color;
          __x_parent->_M_color = _S_rb_tree_black;
          if (__w->_M_right) __w->_M_right->_M_color = _S_rb_tree_black;
          _Rb_tree_rotate_left(__x_parent, __root);
          break;
        }
      } else {
          // __x為右兒子情況,same as above, with _M_right <-> _M_left.
      }

    if (__x) __x->_M_color = _S_rb_tree_black;    // __x為根節點,或者為紅色節點,直接染黑便符合RB_Tree性質
  }
  return __y;
}

附錄3:SGI STL RB_Tree插入修復實現:

 

// RB_Tree插入修復
// 插入節點__x初始置為紅色
inline void 
_Rb_tree_rebalance(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
{
  __x->_M_color = _S_rb_tree_red;
  while (__x != __root && __x->_M_parent->_M_color == _S_rb_tree_red) {        // 父節點p為紅才需要調整(父節點p為黑的話,__x直接插入,不改變RB_Tree性質)
    if (__x->_M_parent == __x->_M_parent->_M_parent->_M_left) {        // 父節點p為祖父節點g的左兒子    
      _Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_right;    // __y指向父節點的兄弟節點,也即叔叔節點
      if (__y && __y->_M_color == _S_rb_tree_red) {        // case1:叔叔節點__y為紅色,執行分支,結果:從case1轉到case2
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __y->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        __x = __x->_M_parent->_M_parent;
      }
      else {
        if (__x == __x->_M_parent->_M_right) {        // case2;叔叔節點__y為黑色,當前節點是父節點p的右兒子,執行分支,結果:從case2轉到case3
          __x = __x->_M_parent;
          _Rb_tree_rotate_left(__x, __root);
        }
        // case3:剩下最后一種情況,叔叔節點__y為黑色,當前節點是父節點p的左兒子,執行分支,結果:RB_Tree重新恢復了平衡
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        _Rb_tree_rotate_right(__x->_M_parent->_M_parent, __root);
      }
    }
    else {
      // 父節點p為祖父節點g的右兒子    
      _Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_left;
      if (__y && __y->_M_color == _S_rb_tree_red) {
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __y->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        __x = __x->_M_parent->_M_parent;
      }
      else {
        if (__x == __x->_M_parent->_M_left) {
          __x = __x->_M_parent;
          _Rb_tree_rotate_right(__x, __root);
        }
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        _Rb_tree_rotate_left(__x->_M_parent->_M_parent, __root);
      }
    }
  }
  __root->_M_color = _S_rb_tree_black;    // 如果插入的是根結點,由於原樹是空樹,此情況只會違反性質2,因此直接把此結點塗為黑色
}

附錄4:SGI STL RB_Tree左旋和右旋實現

inline void 
_Rb_tree_rotate_left(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
{
  _Rb_tree_node_base* __y = __x->_M_right;
  __x->_M_right = __y->_M_left;
  if (__y->_M_left !=0)
    __y->_M_left->_M_parent = __x;
  __y->_M_parent = __x->_M_parent;

  if (__x == __root)
    __root = __y;
  else if (__x == __x->_M_parent->_M_left)
    __x->_M_parent->_M_left = __y;
  else
    __x->_M_parent->_M_right = __y;
  __y->_M_left = __x;
  __x->_M_parent = __y;
}

inline void 
_Rb_tree_rotate_right(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
{
  _Rb_tree_node_base* __y = __x->_M_left;
  __x->_M_left = __y->_M_right;
  if (__y->_M_right != 0)
    __y->_M_right->_M_parent = __x;
  __y->_M_parent = __x->_M_parent;

  if (__x == __root)
    __root = __y;
  else if (__x == __x->_M_parent->_M_right)
    __x->_M_parent->_M_right = __y;
  else
    __x->_M_parent->_M_left = __y;
  __y->_M_right = __x;
  __x->_M_parent = __y;
}

 

【參考1】

1、教你透徹了解紅黑樹

2、紅黑樹算法的實現與剖析

3、紅黑樹的c源碼實現與剖析

4、一步一圖一代碼,R-B Tree

5、紅黑樹插入和刪除結點的全程演示

6、紅黑樹的c++完整實現源碼

【參考2】紅黑樹(刪除)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM