紅黑樹——算法導論(15)


1. 什么是紅黑樹

(1) 簡介

    上一篇我們介紹了基本動態集合操作時間復雜度均為O(h)的二叉搜索樹。但遺憾的是,只有當二叉搜索樹高度較低時,這些集合操作才會較快;即當樹的高度較高(甚至一種極端情況是樹變成了1條鏈)時,這些集合操作並不比在鏈表上執行的快。

    於是我們需要構建出一種“平衡”的二叉搜索樹。

    紅黑樹(red-black tree)正是其中的一種。它可以保證在最壞的情況下,基本集合操作的時間復雜度是O(lgn)。

(2) 性質

    與普通二叉搜索樹不同的是,紅黑樹在每個結點上增加了一個存儲位來表示該結點的顏色(只能是Black或Red中的一種),因此此時一個結點包含5個屬性:color,key,left,right和p。通過對各個結點的顏色進行約束,可以保證任何一條從根到葉子的簡單路徑上不會比其他路徑長2倍(這就保證了“平衡”)。

    這個約束(性質)是:

① 根結點和葉結點是黑色的;

② 紅色結點的子結點必是黑色的;

③ 任何一個結點到其所有后代葉結點的簡單路徑包含相同數目的黑色結點,並稱這個黑色結點的數目(不包含出發結點)為黑高(black-height,用bh(x)表示,紅黑樹的黑高為根結點的黑高)。

下圖是一棵紅黑樹:

 

8394323_1293613306CGzE

    也許你會奇怪上面的紅黑樹並沒有滿足葉結點必須是黑色這條性質呀。事實上它是滿足的,因為上圖畫出的其實是樹的內部結點,我們在真正處理時,會把上圖中的葉結點的左右孩子指向一個值為NIL,顏色為黑色的結點(外部節點),即真正的葉結點是這個黑色的值為NIL的結點,這樣就滿足紅黑樹性質了。如下圖所示:

image

    但是如果采用上面的方法處理,無形中加入了這么多“無用”的結點,勢必會浪費大量的存儲空間。其實我們可以把這些NIL結點合並為一個,就像下圖做的那樣:

image

 但為了方便起見,我們之后的討論將忽略這個值為NIL的結點。

(3) 為什么紅黑樹是一種好的搜索樹

因為

一棵有n個結點的紅黑樹的高度至多為2lg(n+1)。

我們可以先用數學歸納法證明:以任一結點x為根的子樹至少包含2bh(x)-1個內部結點。然后根據約束③便可以得出上述結論(具體證明略)。

    因此可知,動態集合操作Search、Minimum、Maximum、Successor和Predecessor在紅黑樹上可在O(lgn)時間內完成。

    由於紅黑樹其實是一種“平衡”的二叉搜索樹,因此我們只需要研究它的插入和刪除操作,其他操作和二叉搜索樹一致。

2. 旋轉

    在研究插入操作之前,我們先來介紹旋轉操作。

    我們在紅黑樹上進行Insert、Delete操作時,會因為修改了樹的結構而導致違背上述約束。這時就需要修改樹中某些結點的顏色和指針結構來維護紅黑樹的性質。

    指針結構的修改是通過旋轉完成的。下圖是兩種旋轉操作:左旋和右旋的示意圖。

image

下面給出左旋操作的偽代碼:

image

可以看出,左旋(右旋也類似)操作可以在O(1)時間內完成。

下圖是一個左旋操作的實際例子:

image

3. 插入

(1) 算法

    我們可以用類似於二叉搜索樹的方法來向樹中插入一個元素。它可以在O(lgn)時間內完成。插入算法的描述如下:

image

可以看出我們默認把新插入的結點着為紅色插入(原因之后給出)。與二叉搜索樹的插入算法最為不同的是,在最后一步我們調用了RB-INSERT-FIXUP方法來維護紅黑樹的性質。下面給出它的具體描述:

image

image

下圖是一個范例:

image

(2) 分析

上述過程可能有些復雜,我們來仔細分析一下,從兩個方面入手。

    第一,我們應當明確RB-INSERT-FIXUP方法是來維護紅黑樹的性質的,因此我們要搞清楚插入一個結點(紅色)將會打破哪些性質;

    第二,我們要具體分析上述的三種情況究竟在做什么有什么影響

① 哪些性質會被破壞

    很明顯,只有性質①——根結點必須是黑色(當且僅當插入時樹為空會發生這種情形)和性質②——紅色結點的孩子必為黑色會被破壞。

② 三種情況

    首先,循環的大前提是z的父結點是紅色。然后,我們分析的三種情況建立在z的父結點是左孩子基礎上(相反情況類似,不做分析)。

    我們還容易看出:在每次迭代前,z結點總是紅色的;y結點為z結點的“叔叔”(y = z.p.p.right,下面稱y為z的叔結點)。

    Case 1:z的叔結點y為紅色(同時也說明了z的“爺爺”結點是黑色的)。在做什么:把z的“爺爺”結點着為紅色;而把“爺爺”結點的子結點都着為黑色;z上升2級,指向它的“爺爺”結點。有什么影響:以上操作對任何一條簡單路徑的黑高都不會產生影響;操作其實並沒有讓情況得到“改善”,只是使z上升了2級。

    Case 2:z的叔結點y為黑色且z為右孩子。在做什么:z指向自己的父結點;對z結點進行左旋操作。有什么影響:以上操作對任何一條簡單路徑的黑高都不會產生影響,只是將Case 2變為了Case 3。

    Case 3:z的叔結點y為黑色且z為左孩子。在做什么:把z的父結點置為黑色;把z的“爺爺”結點置為紅色;對z的“爺爺”結點進行右旋操作。

有什么影響:在兩次修改顏色后,會導致從根結點向左出發的所有路徑的黑高加1,而向右出發的所有路徑的黑高不變;而右旋操作會使黑高回歸平衡。

(3) 證明

下面我們再用循環不變式(關於循環不變式見算法基礎——算法導論(1))來分析(證明)上述過程:

這個不變式是:

1) 結點z是紅色的;

2) 最多僅有1條紅黑性質被打破,要么是性質①——根結點為黑色被打破;要么是性質②——紅結點的孩子必須是黑色被打破。

初始化

    由於我們默認把新插入的結點置為紅色,因此初始時,結點z是紅色顯然成立。

    在迭代之前,如果只有一個結點,即只有根結點,性質①被打破;否則,z和z.p都為紅色,性質②被打破;

    綜上所述,不變式在初始時成立。

保持:

    從上面對三種情況的分析我們可以看出:結點z始終是紅色的;三種操作都沒有改變任何路徑上的黑高,即性質③始終是滿足的。顯然每次完成case 1后,性質①或性質②是被打破的。完成case 2一樣。當完成case 3后,循環就終止了(因為在case 3中我們把z.p置為了黑色),此時滿足紅黑性質。

    不變式始終成立。

終止:

    迭代終止的條件是:z.p為黑色。通過保持性分析我們看出,終止只可能發生在執行完case 1和case 3情形后。而在執行完case 1后z為紅色,z.p為黑色,因此性質②不可能被打破,那么只可能是性質①被打破;在執行完case 3后,就已經滿足所有紅黑性質,即已經是一棵合法的紅黑樹。

    由上述分析,我們可以得出:在循環結束后,要么二叉樹已經是一棵合法的紅黑樹;要么只有性質①——根結點為黑色被打破。

    於是在循環結束后,我們只需要做一次將根結點置為黑色,那么便修正了紅黑樹的合法性。

(4) 說明

    由於一棵有n個結點的紅黑樹的高度為O(lg n),因此執行RB—INSERT的前16行需要O(lg n)時間;在RB-INSERT-FIXUP中,僅當case 1發生時,while循環才會執行下去;而每次執行完case 1,指針z都會上升2層。因此while循環最多執行lg n次;所以RB-INSERT-FIXUP時間復雜度為O(lg n),因此整個插入操作的時間復雜度為O(lg n)。

4. 刪除

(1) 算法

    同二叉搜索樹一樣,我們先給出TRANSPLANT方法,該方法會用以v為根結點的子樹替換以u為根結點的子樹:image

    下面給出刪除操作的算法描述:

image

    可以看出,以上刪除操作與普通二叉搜索樹相比沒有太大差別。其中最大的差別是以上操作在22行加了一個維護紅黑性質的過程,RB-DELETE_FINXUP,該操作的過程如下:

image

(2) 分析

RB-DELETE也分了三種不同的情況,其中第三種情況又分了兩小種,分別依次對應如下圖所示的情形:QQ截圖20151027214543

QQ截圖20151027214717

 

image

image

    同樣我們還是要分析各種情況什么有什么影響(對紅黑性質而言)。

    可以發現上圖其實就是普通二叉搜索樹在刪除時的分類情況,事實上,以上的代碼完全包含了普通二叉搜索樹刪除操作的代碼,只是在其基礎上加上了維護紅黑性質的代碼。因此對於這些重復的代碼做了什么我們不再分析。

    我們在每次刪除前,對於上面前兩幅圖中的情況,我們會記錄下被刪除結點z的顏色,因為它決定了我們最后是否需要修正紅黑性質(若z為紅色,其父結點和孩子必為黑色,刪除z將不會違背性質③,用z的孩子去替代z也不會影響性質②),y指向z,x指向z的右孩子;對於后兩種情況,我們記錄的是被刪除結點z右子樹中關鍵字最小的結點y的顏色,同樣,如果它是紅色,不管z是什么顏色,統一將y的顏色修改為z的顏色,並按照圖中的方式去置換掉z,都不會對紅黑性質產生影響。綜合上述分析,我們發現四種情況的“輸出”(處理后的結果)是一致的:若記錄的顏色是紅色,說明紅黑性質未改變;如果是黑色,說明紅黑性質一定被打破了。具體的說,如果結點y是黑色,如下圖,將會造成3種影響:

image

1)如果y原來是根結點,而y的一個紅孩子成為了新的根結點,將會違背性質①。如上圖①中左側情況。

2)如果x和x的父結點都為紅色,將違背性質②。如上圖③中的左側,x為紅色的情況。

3)所有的情況(除了y原來是根結點外)都將導致之前包含y結點的簡單路徑上的黑結點數少1,將違背性質③。修正這一問題的方式是我們將現在占據y結點位置的x結點“再塗上一層黑色”,當然,“塗色”操作並不反映在代碼上,即我們不會修改x的color屬性,我們只是“在心中記住”,適當的時候會把x的這層黑色塗到某個紅色結點上以達到目的。“塗了兩層色”的x結點可能是雙層黑色或紅黑色,它們分別會“貢獻”2或1個黑色結點數。

 

下面我們再分析RB-DELETE-FIXUP修正過程:

修正過程分了4種情況,我們先給出每種情況對應的示意圖:

image

然后對每種情況給出分析(建立在x是左孩子的基礎上):

Case 1:x的右兄弟w是紅色,說明x的父結點一定是黑色。所作的操作是:交換w和其父結點的顏色,即把w換為黑色,其父結點換位紅色;然后對父結點左旋,w重新指向x的右兄弟(該結點原本是w的左孩子,所以一定為黑色)。這是Case 1過度到Case 2。

Case 2:w的孩子都為黑色(w也是黑色)。所作的操作是:將w換為紅色,x指向其父結點。

Case 3:w的左孩子是紅色,右孩子是黑色(w也是黑色)。所作的操作是:交換w和其左孩子的顏色,即把w換位紅色,其左孩子換為黑色;然后對w右旋,w重新指向x的右兄弟。

Case 4:w的右孩子是黑色(w是黑色)。w與x的父結點交換顏色;並把w的右孩子設為黑色,對x的父結點左旋,x直接指向根結點,循環結束。

做完以上的while循環,我們還要做的一步操作是將根結點置為黑色。這樣就能保證滿足性質①。

可以證明做完上述操作,所有的紅黑性質便滿足了。具體證明過程和插入操作時的證明類似,這里省略。

我們也不難分析出RB-DELETE的時間復雜度是O(lg n)。

5. 小結

    像上面那樣,我們就可以構造出一棵紅黑樹了。它能保證基本集合操作的時間復雜度為O(lg n)。

    先記錄到這里,以后再給出普通二叉搜索樹和紅黑樹的Java實現代碼。


免責聲明!

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



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