1. 程式人生 > >傻瓜都能看懂,30張圖徹底理解紅黑樹!

傻瓜都能看懂,30張圖徹底理解紅黑樹!

blank 學會 對稱 圖文 應該 51cto 徹底 三種 一個

當在 10 億數據中只需要進行十幾次比較就能查找到目標時,不禁感嘆編程之魅力!人類之偉大呀!

—學紅黑樹有感

技術分享圖片

終於,在學習了幾天的紅黑樹相關的知識後,我想把我所學所想和所感分享給大家。

技術分享圖片

紅黑樹是一種比較難的數據結構,要完全搞懂非常耗時耗力,紅黑樹怎麽自平衡?什麽時候需要左旋或右旋?插入和刪除破壞了樹的平衡後怎麽處理?等等一連串的問題在學習前困擾著我。

如果你在學習過程中也會存在我的疑問,那麽本文對你會有幫助,本文幫助你全面、徹底地理解紅黑樹!

本文將通過圖文的方式講解紅黑樹的知識點,並且不會涉及到任何代碼,相信我,在懂得紅黑樹實現原理前,看代碼會一頭霧水的,當原理懂了,代碼也就按部就班寫而已,沒任何難度。

閱讀本文你需具備知識點:

  • 二叉查找樹
  • 完美平衡二叉樹

紅黑樹也是二叉查找樹,我們知道,二叉查找樹這一數據結構並不難,而紅黑樹之所以難是難在它是自平衡的二叉查找樹,在進行插入和刪除等可能會破壞樹的平衡的操作時,需要重新自處理達到平衡狀態。

現在在腦海想下怎麽實現?是不是太多情景需要考慮了?嘖嘖,先別急,通過本文的學習後,你會覺得,其實也不過如此而已。好吧,我們先來看下紅黑樹的定義和一些基本性質。

紅黑樹定義和性質

紅黑樹是一種含有紅黑結點並能自平衡的二叉查找樹。它必須滿足下面性質:

  • 每個節點要麽是黑色,要麽是紅色。
  • 根節點是黑色。
  • 每個葉子節點(Nil)是黑色。
  • 每個紅色結點的兩個子結點一定都是黑色。
  • 任意一結點到每個葉子結點的路徑都包含數量相同的黑結點。

從性質 5 又可以推出:如果一個結點存在黑子結點,那麽該結點肯定有兩個子結點。

技術分享圖片

圖 1:一棵簡單的紅黑樹

上圖就是一顆簡單的紅黑樹。其中 Nil 為葉子結點,並且它是黑色的。(值得提醒註意的是,在 Java 中,葉子結點是為 null 的結點。)

紅黑樹並不是一個完美平衡二叉查找樹,從圖 1 可以看到,根結點 P 的左子樹顯然比右子樹高。

但左子樹和右子樹的黑結點的層數是相等的,也即任意一個結點到到每個葉子結點的路徑都包含數量相同的黑結點(性質 5)。所以我們叫紅黑樹這種平衡為黑色完美平衡。

介紹到此,為了後面講解不至於混淆,我們還需要來約定下紅黑樹一些結點的叫法,如圖 2 所示:

技術分享圖片

圖 2:結點叫法約定

我們把正在處理(遍歷)的結點叫做當前結點,如圖 2 中的 D,它的父親叫做父結點,它的父親的另外一個子結點叫做兄弟結點,父親的父親叫做祖父結點。

前面講到紅黑樹能自平衡,它靠的是什麽?有如下三種操作:

左旋:以某個結點作為支點(旋轉結點),其右子結點變為旋轉結點的父結點,右子結點的左子結點變為旋轉結點的右子結點,左子結點保持不變。如圖 3。

右旋:以某個結點作為支點(旋轉結點),其左子結點變為旋轉結點的父結點,左子結點的右子結點變為旋轉結點的左子結點,右子結點保持不變。如圖 4。

變色:結點的顏色由紅變黑或由黑變紅。

技術分享圖片

圖 3:左旋

技術分享圖片

圖 4:右旋

上面所說的旋轉結點也即旋轉的支點,圖 4 和圖 5 中的 P 結點。我們先忽略顏色,可以看到旋轉操作不會影響旋轉結點的父結點,父結點以上的結構還是保持不變的。

左旋只影響旋轉結點和其右子樹的結構,把右子樹的結點往左子樹挪了;右旋只影響旋轉結點和其左子樹的結構,把左子樹的結點往右子樹挪了。

所以旋轉操作是局部的。另外可以看出旋轉能保持紅黑樹平衡的一些端詳了:當一邊子樹的結點少了,那麽向另外一邊子樹“借”一些結點;當一邊子樹的結點多了,那麽向另外一邊子樹“租”一些結點。

但要保持紅黑樹的性質,結點不能亂挪,還得靠變色了。怎麽變?具體情景有不同變法,後面會具體講到,現在只需要記住紅黑樹總是通過旋轉和變色達到自平衡。

Balabala 了這麽多,相信你對紅黑樹有一定印象了,那麽現在來考考你。

思考題 1:黑結點可以同時包含一個紅子結點和一個黑子結點嗎? (答案見文末)

接下來先講解紅黑樹的查找熱熱身。

紅黑樹查找

因為紅黑樹是一顆二叉平衡樹,並且查找不會破壞樹的平衡,所以查找跟二叉平衡樹的查找無異:

從根結點開始查找,把根結點設置為當前結點。

  • 若當前結點為空,返回 null。
  • 若當前結點不為空,用當前結點的 key 跟查找 key 作比較。
  • 若當前結點 key 等於查找 key,那麽該 key 就是查找目標,返回當前結點。
  • 若當前結點 key 大於查找 key,把當前結點的左子結點設置為當前結點,重復步驟 2。
  • 若當前結點 key 小於查找 key,把當前結點的右子結點設置為當前結點,重復步驟 2。

如圖 5 所示:

技術分享圖片

圖 5:二叉樹查找流程圖

非常簡單,但簡單不代表它效率不好。正由於紅黑樹總保持黑色完美平衡,所以它的查找最壞時間復雜度為 O(2lgN),也即整顆樹剛好紅黑相隔的時候。

能有這麽好的查找效率得益於紅黑樹自平衡的特性,而這背後的付出,紅黑樹的插入操作功不可沒。

紅黑樹插入

插入操作包括兩部分工作:一是查找插入的位置;二是插入後自平衡。

查找插入的父結點很簡單,跟查找操作區別不大:

  • 從根結點開始查找。
  • 若根結點為空,那麽插入結點作為根結點,結束。
  • 若根結點不為空,那麽把根結點作為當前結點。
  • 若當前結點為 null,返回當前結點的父結點,結束。
  • 若當前結點 key 等於查找 key,那麽該 key 所在結點就是插入結點,更新結點的值,結束。
  • 若當前結點 key 大於查找 key,把當前結點的左子結點設置為當前結點,重復步驟 4。
  • 若當前結點 key 小於查找 key,把當前結點的右子結點設置為當前結點,重復步驟 4。

如圖 6 所示:

技術分享圖片

圖 6:紅黑樹插入位置查找

OK,插入位置已經找到,把插入結點放到正確的位置就可以啦,但插入結點應該是什麽顏色呢?

答案是紅色。理由很簡單,紅色在父結點(如果存在)為黑色結點時,紅黑樹的黑色平衡沒被破壞,不需要做自平衡操作。

但如果插入結點是黑色,那麽插入位置所在的子樹黑色結點總是多 1,必須做自平衡。

所有插入情景如圖 7 所示:

技術分享圖片

圖 7:紅黑樹插入情景

嗯,插入情景很多呢,8 種插入情景!但情景 1、2 和 3 的處理很簡單,而情景 4.2 和情景 4.3 只是方向反轉而已。

懂得了一種情景就能推出另外一種情景,所以總體來看,並不復雜,後續我們將一個一個情景來看,把它徹底搞懂。

另外,根據二叉樹的性質,除了情景 2,所有插入操作都是在葉子結點進行的。這點應該不難理解,因為查找插入位置時,我們就是在找子結點為空的父結點的。

在開始每個情景的講解前,我們還是先來約定下,如圖 8 所示:

技術分享圖片

圖 8:插入操作結點的叫法約定

圖 8 的字母並不代表結點 Key 的大小。I 表示插入結點,P 表示插入結點的父結點,S 表示插入結點的叔叔結點,PP 表示插入結點的祖父結點。

好了,下面讓我們一個一個來分析每個插入的情景以及處理。

情景 1:紅黑樹為空樹

最簡單的一種情景,直接把插入結點作為根結點就行,但註意,根據紅黑樹性質 2:根節點是黑色。還需要把插入結點設為黑色。

處理:把插入結點作為根結點,並把結點設置為黑色。

情景 2:插入結點的 Key 已存在

插入結點的 Key 已存在,既然紅黑樹總保持平衡,在插入前紅黑樹已經是平衡的,那麽把插入結點設置為將要替代結點的顏色,再把結點的值更新就完成插入。

處理:把 I 設為當前結點的顏色,更新當前結點的值為插入結點的值。

情景 3:插入結點的父結點為黑結點

由於插入的結點是紅色的,當插入結點是黑色時,並不會影響紅黑樹的平衡,直接插入即可,無需做自平衡。

處理:直接插入。

情景 4:插入結點的父結點為紅結點

再次回想下紅黑樹的性質 2:根結點是黑色。如果插入的父結點為紅結點,那麽該父結點不可能為根結點,所以插入結點總是存在祖父結點。這點很重要,因為後續的旋轉操作肯定需要祖父結點的參與。

情景 4 又分為很多子情景,下面將進入重點部分,各位看官請留神了。

插入情景 4.1:叔叔結點存在並且為紅結點。

從紅黑樹性質 4 可以,祖父結點肯定為黑結點,因為不可以同時存在兩個相連的紅結點。

那麽此時該插入子樹的紅黑層數的情況是:黑紅紅。顯然最簡單的處理方式是把其改為:紅黑紅。如圖 9 和圖 10 所示。

處理:將 P 和 S 設置為黑色,將 PP 設置為紅色,把 PP 設置為當前插入結點。

技術分享圖片

圖 9:插入情景 4.1_1

技術分享圖片

圖 10:插入情景 4.1_2

可以看到,我們把 PP 結點設為紅色了,如果 PP 的父結點是黑色,那麽無需再做任何處理。

但如果 PP 的父結點是紅色,根據性質 4,此時紅黑樹已不平衡了,所以還需要把 PP 當作新的插入結點,繼續做插入操作自平衡處理,直到平衡為止。

試想下 PP 剛好為根結點時,那麽根據性質 2,我們必須把 PP 重新設為黑色,那麽樹的紅黑結構變為:黑黑紅。

換句話說,從根結點到葉子結點的路徑中,黑色結點增加了。這也是唯一一種會增加紅黑樹黑色結點層數的插入情景。

我們還可以總結出另外一個經驗:紅黑樹的生長是自底向上的。這點不同於普通的二叉查找樹,普通的二叉查找樹的生長是自頂向下的。

插入情景 4.2:叔叔結點不存在或為黑結點,並且插入結點的父親結點是祖父結點的左子結點。

單純從插入前來看,也即不算情景 4.1 自底向上處理時的情況,叔叔結點非紅即為葉子結點(Nil)。

因為如果叔叔結點為黑結點,而父結點為紅結點,那麽叔叔結點所在的子樹的黑色結點就比父結點所在子樹的多了,這不滿足紅黑樹的性質 5。後續情景同樣如此,不再多做說明了。

前文說了,需要旋轉操作時,肯定一邊子樹的結點多了或少了,需要租或借給另一邊。插入顯然是多的情況,那麽把多的結點租給另一邊子樹就可以了。

插入情景 4.2.1:插入結點是其父結點的左子結點。

處理:將 P 設為黑色,將 PP 設為紅色,對 PP 進行右旋。

技術分享圖片

圖 11:插入情景 4.2.1

由圖 11 可得,左邊兩個紅結點,右邊不存在,那麽一邊一個剛剛好,並且因為為紅色,肯定不會破壞樹的平衡。

咦,可以把 PP 設為紅色,I 和 P 設為黑色嗎?答案是可以!看過《算法:第 4 版》的同學可能知道,書中講解的就是把 PP 設為紅色,I 和 P 設為黑色。

但把 PP 設為紅色,顯然又會出現情景 4.1 的情況,需要自底向上處理,做多了無謂的操作,既然能自己消化就不要麻煩祖輩們啦。

插入情景 4.2.2:插入結點是其父結點的右子結點。

這種情景顯然可以轉換為情景 4.2.1,如圖 12 所示,不做過多說明了。

技術分享圖片

圖 12:插入情景 4.2.2

處理:對 P 進行左旋,把 P 設置為插入結點,得到情景 4.2.1,進行情景 4.2.1 的處理。

插入情景 4.3:叔叔結點不存在或為黑結點,並且插入結點的父親結點是祖父結點的右子結點。

該情景對應情景 4.2,只是方向反轉,不做過多說明了,直接看圖。

插入情景 4.3.1:插入結點是其父結點的右子結點。

技術分享圖片

圖 13:插入情景 4.3.1

處理:將 P 設為黑色,將 PP 設為紅色,對 PP 進行左旋。

插入情景 4.3.2:插入結點是其父結點的右子結點。

技術分享圖片

圖 14:插入情景 4.3.2

處理:對 P 進行右旋,把 P 設置為插入結點,得到情景 4.3.1,進行情景 4.3.1 的處理。

好了,講完插入的所有情景了。可能有同學會想:上面的情景舉例的都是第一次插入而不包含自底向上處理的情況,那麽上面所說的情景都適合自底向上的情況嗎?答案是肯定的。

理由很簡單,只要每棵子樹都能自平衡,那麽整棵樹最終總是平衡的。好吧,在出個習題,請大家拿出筆和紙畫下試試(請務必動手畫下,加深印象)。

習題 1:請畫出圖 15 的插入自平衡處理過程。(答案見文末)

技術分享圖片

圖 15:習題 1

紅黑樹刪除

紅黑樹插入已經夠復雜了,但刪除更復雜,也是紅黑樹最復雜的操作了。但穩住,勝利的曙光就在前面了!

紅黑樹的刪除操作也包括兩部分工作:一是查找目標結點;二是刪除後自平衡。

查找目標結點顯然可以復用查找操作,當不存在目標結點時,忽略本次操作;當存在目標結點時,刪除後就得做自平衡處理了。

刪除了結點後,我們還需要找結點來替代刪除結點的位置,不然子樹跟父輩結點斷開了,除非刪除結點剛好沒子結點,那麽就不需要替代。

二叉樹刪除結點找替代結點有 3 種情景:

  • 若刪除結點無子結點,直接刪除。
  • 若刪除結點只有一個子結點,用子結點替換刪除結點。
  • 若刪除結點有兩個子結點,用後繼結點(大於刪除結點的最小結點)替換刪除結點。

補充說明下,情景 3 的後繼結點是大於刪除結點的最小結點,也是刪除結點的右子樹中最右結點。

那麽可以拿前繼結點(刪除結點的左子樹最左結點)替代嗎?可以的。但習慣上大多都是拿後繼結點來替代,後文的講解也是用後繼結點來替代。

另外告訴大家一種找前繼和後繼結點的直觀的方法(不知為何沒人提過,大家都知道?):把二叉樹所有結點投射在X軸上,所有結點都是從左到右排好序的,所有目標結點的前後結點就是對應前繼和後繼結點。

如圖 16 所示:

技術分享圖片

圖 16:二叉樹投射 x 軸後有序

接下來,講一個重要的思路:刪除結點被替代後,在不考慮結點的鍵值的情況下,對於樹來說,可以認為刪除的是替代結點!話很蒼白,我們看圖 17。

技術分享圖片

圖 17:刪除結點換位思路

在不看鍵值對的情況下,圖 17 的紅黑樹最終結果是刪除了 Q 所在位置的結點!這種思路非常重要,大大簡化了後文講解紅黑樹刪除的情景!

基於此,上面所說的 3 種二叉樹的刪除情景可以相互轉換並且最終都是轉換為情景 1。

情景 2:刪除結點用其唯一的子結點替換,子結點替換為刪除結點後,可以認為刪除的是子結點,若子結點又有兩個子結點,那麽相當於轉換為情景 3,一直自頂向下轉換,總是能轉換為情景 1。(對於紅黑樹來說,根據性質 5.1,只存在一個子結點的結點肯定在樹末了)

情景 3:刪除結點用後繼結點(肯定不存在左結點),如果後繼結點有右子結點,那麽相當於轉換為情景 2,否則轉為情景 1。

二叉樹刪除結點情景關系圖如圖 18 所示:

技術分享圖片

圖 18:二叉樹刪除情景轉換

綜上所述,刪除操作刪除的結點可以看作刪除替代結點,而替代結點最後總是在樹末。

有了這結論,我們討論的刪除紅黑樹的情景就少了很多,因為我們只考慮刪除樹末結點的情景了。

同樣的,我們也是先來總體看下刪除操作的所有情景,如圖 19 所示:

技術分享圖片

圖 19:紅黑樹刪除情景

哈哈,是的,即使簡化了還是有 9 種情景!但跟插入操作一樣,存在左右對稱的情景,只是方向變了,沒有本質區別。同樣的,我們還是來約定下,如圖 20 所示:

技術分享圖片

圖 20:刪除操作結點的叫法約定

圖 20 的字母並不代表結點 Key 的大小。R 表示替代結點,P 表示替代結點的父結點,S 表示替代結點的兄弟結點,SL 表示兄弟結點的左子結點,SR 表示兄弟結點的右子結點。灰色結點表示它可以是紅色也可以是黑色。

值得特別提醒的是,R 是即將被替換到刪除結點的位置的替代結點,在刪除前,它還在原來所在位置參與樹的子平衡,平衡後再替換到刪除結點的位置,才算刪除完成。

萬事俱備,我們進入最後的也是最難的講解。

刪除情景 1:替換結點是紅色結點。

我們把替換結點換到了刪除結點的位置時,由於替換結點是紅色,刪除了也不會影響紅黑樹的平衡,只要把替換結點的顏色設為刪除的結點的顏色即可重新平衡。

處理:顏色變為刪除結點的顏色。

刪除情景 2:替換結點是黑結點。

當替換結點是黑色時,我們就不得不進行自平衡處理了。我們必須還得考慮替換結點是其父結點的左子結點還是右子結點,來做不同的旋轉操作,使樹重新平衡。

刪除情景 2.1:替換結點是其父結點的左子結點。

刪除情景 2.1.1:替換結點的兄弟結點是紅結點。

技術分享圖片

圖 21:刪除情景 2.1.1

若兄弟結點是紅結點,那麽根據性質 4,兄弟結點的父結點和子結點肯定為黑色,不會有其他子情景,我們按圖 21 處理,得到刪除情景 2.1.2.3(後續講解,這裏先記住,此時 R 仍然是替代結點,它的新的兄弟結點 SL 和兄弟結點的子結點都是黑色)。

處理:將 S 設為黑色,將 P 設為紅色,對 P 進行左旋,得到情景 2.1.2.3,進行情景 2.1.2.3 的處理。

刪除情景 2.1.2:替換結點的兄弟結點是黑結點。

當兄弟結點為黑色時,其父結點和子結點的具體顏色也無法確定(如果也不考慮自底向上的情況,子結點非紅即為葉子結點Nil,Nil結點為黑結點),此時又得考慮多種子情景。

刪除情景 2.1.2.1:替換結點的兄弟結點的右子結點是紅結點,左子結點任意顏色。

技術分享圖片

圖 22:刪除情景 2.1.2.1

即將刪除的左子樹的一個黑色結點,顯然左子樹的黑色結點少 1 了,然而右子樹又有紅色結點,那麽我們直接向右子樹“借”個紅結點來補充黑結點就好啦,此時肯定需要用旋轉處理了,如圖 22 所示。

處理:將 S 的顏色設為 P 的顏色,將 P 設為黑色,將 SR 設為黑色,對 P 進行左旋。

平衡後的圖怎麽不滿足紅黑樹的性質?前文提醒過,R 是即將替換的,它還參與樹的自平衡,平衡後再替換到刪除結點的位置,所以 R 最終可以看作是刪除的。

另外圖 2.1.2.1 是考慮到第一次替換和自底向上處理的情況,如果只考慮第一次替換的情況,根據紅黑樹性質,SL 肯定是紅色或為 Nil,所以最終結果樹是平衡的。

如果是自底向上處理的情況,同樣,每棵子樹都保持平衡狀態,最終整棵樹肯定是平衡的。後續的情景同理,不做過多說明了。

刪除情景 2.1.2.2:替換結點的兄弟結點的右子結點為黑結點,左子結點為紅結點。

兄弟結點所在的子樹有紅結點,我們總是可以向兄弟子樹借個紅結點過來,顯然該情景可以轉換為情景 2.1.2.1。如圖 23 所示:

技術分享圖片

圖 23:刪除情景 2.1.2.2

處理:將 S 設為紅色,將 SL 設為黑色,對 S 進行右旋,得到情景 2.1.2.1,進行情景 2.1.2.1 的處理。

刪除情景 2.1.2.3:替換結點的兄弟結點的子結點都為黑結點。

好了,此次兄弟子樹都沒紅結點“借”了,兄弟幫忙不了,找父母唄,這種情景我們把兄弟結點設為紅色,再把父結點當作替代結點,自底向上處理,去找父結點的兄弟結點去“借”。

但為什麽需要把兄弟結點設為紅色呢?顯然是為了在 P 所在的子樹中保證平衡(R 即將刪除,少了一個黑色結點,子樹也需要少一個),後續的平衡工作交給父輩們考慮了,還是那句,當每棵子樹都保持平衡時,最終整棵總是平衡的。

技術分享圖片

圖 24:情景 2.1.2.3

處理:將 S 設為紅色,把 P 作為新的替換結點,重新進行刪除結點情景處理。

刪除情景 2.2:替換結點是其父結點的右子結點。

好啦,右邊的操作也是方向相反,不做過多說明了,相信理解了刪除情景 2.1 後,肯定可以理解 2.2。

刪除情景 2.2.1:替換結點的兄弟結點是紅結點。

技術分享圖片

圖 25:刪除情景 2.2.1

處理:將 S 設為黑色,將 P 設為紅色,對 P 進行右旋,得到情景 2.2.2.3,進行情景 2.2.2.3 的處理。

刪除情景 2.2.2:替換結點的兄弟結點是黑結點。

刪除情景 2.2.2.1:替換結點的兄弟結點的左子結點是紅結點,右子結點任意顏色。

技術分享圖片

圖 26:刪除情景 2.2.2.1

處理:將 S 的顏色設為 P 的顏色,將 P 設為黑色,將 SL 設為黑色,對 P 進行右旋。

刪除情景 2.2.2.2:替換結點的兄弟結點的左子結點為黑結點,右子結點為紅結點。

技術分享圖片

圖 27:刪除情景 2.2.2.2

處理:將 S 設為紅色,將 SR 設為黑色,對 S 進行左旋,得到情景 2.2.2.1,進行情景 2.2.2.1 的處理。

刪除情景 2.2.2.3:替換結點的兄弟結點的子結點都為黑結點。

技術分享圖片

圖 28:刪除情景 2.2.2.3

處理:將 S 設為紅色,把 P 作為新的替換結點,重新進行刪除結點情景處理。

綜上,紅黑樹刪除後自平衡的處理可以總結為:

自己能搞定的自消化(情景 1)

自己不能搞定的叫兄弟幫忙(除了情景 1、情景 2.1.2.3 和情景 2.2.2.3)

兄弟都幫忙不了的,通過父母,找遠方親戚(情景2.1.2.3和情景2.2.2.3)

哈哈,是不是跟現實中很像,當我們有困難時,首先先自己解決,自己無力了找兄弟姐妹幫忙,如果連兄弟姐妹都幫不上,再去找遠方的親戚了。這樣記憶應該會好記點~

最後再做個習題加深理解(請不熟悉的同學務必動手畫下)。

習題 2:請畫出圖 29 的刪除自平衡處理過程。

技術分享圖片

習題 2

寫在後面

相信看了這篇文章後,再去看 Java 和 HashMap 和 TreeMap 的源碼絕對沒難度!最後來看下思考題和習題的答案吧。

思考題 1:黑結點可以同時包含一個紅子結點和一個黑子結點嗎?

答:可以。如下圖的 F 結點:

技術分享圖片

習題 1:請畫出圖 15 的插入自平衡處理過程。

答案如下圖:

技術分享圖片

習題 2:請畫出圖 29 的刪除自平衡處理過程。

答案如下圖:

技術分享圖片

傻瓜都能看懂,30張圖徹底理解紅黑樹!