紅黑樹是一個比較複雜的數據結構,相信不少人也只知其名而不知其意,由於理解它的原理確實須要花費必定的功夫。之因此寫這篇文章,也是爲了更好的理解 Java 中 TreeMap 的源碼。html
寫以前,搜了下網上的文章,說實話,看完有點懵,大部分一上來就給你它的五大性質,而後就是一頓插入、刪除、旋轉操做,就完事了,理解起來至關吃力。node
本文將結合 2-3-4 樹,按部就班地介紹紅黑樹的由來和原理,相信看完以後,你對它會有更清晰的認識。此外,這裏描述的是普通紅黑樹,而不是它的變體左傾紅黑樹(LLRB),這一點須要注意。算法
紅黑樹的由來要從二叉查找樹提及。二叉查找樹是一顆二叉樹,它每一個結點的值都大於其左子樹的任意結點而小於右子樹的任意結點,它結合了鏈表插入的靈活性和有序數組查找的高效性(二分查找)。編程
對於使用二叉查找樹的算法,它的運行時間取決於樹的形狀,而樹的形狀又取決於結點插入的前後順序。如上圖所示,最好狀況下,N 個結點的樹是徹底平衡的,每條空連接到根結點的距離都爲 ~lgN;而在最壞的狀況下,搜索路徑上可能有 N 個結點,退化成了鏈表。數組
因此,爲了保證運行時間始終在對數級別,在動態構建二叉查找樹時,但願保持其平衡性,也就是下降樹的高度,使其儘量爲 ~lgN,這樣就能保證全部的查找都能在 ~lgN 次比較內結束,就像二分查找那樣,這樣的樹被稱爲平衡二叉查找樹。數據結構
第一個自平衡二叉查找樹就是AVL 樹,它規定,每一個結點的左右子樹的高度之差不超過 1。在插入或刪除結點,打破平衡後,就會經過一次或屢次樹旋轉來從新平衡。編程語言
AVL 樹是嚴格平衡的,適用於查找密集型應用程序,由於在頻繁插入或刪除結點的場景下,它花費在樹旋轉的代價過高。源碼分析
而紅黑樹就是一種折中方案,它不追求完美平衡,只求部分達到平衡,從而下降在調整時樹旋轉次數。網站
說到紅黑樹,就不得不提 2-3-4 樹,由於,紅黑樹能夠說就是它的一種特殊實現,對它有所瞭解,很是有助於理解紅黑樹。3d
保持平衡,無非是爲了下降樹的高度,若是把二叉查找樹通常化,容許一個結點保存多個值,變成多叉樹,也可認爲是下降了高度。
確切地說,標準二叉查找樹中的結點稱爲2-結點(一個值兩個子結點),如今引入3-結點(兩個值三個子結點)和4-結點(三個值四個子結點),這樣就能獲得一顆 2-3-4 樹(也稱爲 2-4 樹)。
2-3-4 樹是 4 階 B 樹,全部數據按排序順序保存,全部葉子結點都在相同的深度。對於大多數編程語言,直接實現 2-3-4 樹比較困難,而紅黑樹的實現相對要簡單容易,這也是紅黑樹應用普遍的一部分緣由。
紅黑樹是二叉樹,全部的結點都是2-結點,因此爲了可以表示3-結點和4-結點,爲結點引入了顏色屬性:
如上圖所示,若是把紅黑樹的紅色結點和其父結點放平,它的結構就和左邊的 2-3-4 樹同樣。
如今,來看下紅黑樹的性質:
這些性質沒必要去背,就算記住後也絕對會忘,應該結合着 2-3-4 樹理解性記憶。
另外,紅黑樹中的旋轉和顏色翻轉,就至關於 2-3-4 樹中的拆分和合併,而且 2-3-4 樹結點的拆分和合並,理解起來至關簡單。對比分析和理解紅黑樹的操做,絕對讓你眼前一亮。
在分析插入和刪除以前,先了解下什麼是樹旋轉。樹旋轉是二叉樹中調整子樹的一種操做,經常使用於調整樹的局部平衡性,它包含兩種方式,左旋轉和右旋轉。
其實旋轉操做很容易理解:左旋轉就是將用兩個結點中的較小者做爲根結點變爲將較大者做爲根結點,右旋轉恰好於此相反,如上圖所示:
紅黑樹的旋轉其實就是爲了確保和其結構相同的 2-3-4 樹的一一對應關係,同時保證紅黑樹的有序性和平衡性。
接下來,就結合 2-4 樹分析結點的插入,首先 2-4 樹的插入邏輯是這樣的:
2-4 樹插入的都是葉子結點,紅黑樹插入的結點都是紅色的,由於在 2-4 樹中,待插入結點都認爲能夠插入到一個多值結點中。
這裏假設待插入結點爲 N,P 是 N 的父結點,G 是 N 的祖父結點,U 是 N 的叔叔結點(即父結點的兄弟結點),那麼紅黑樹有如下幾種插入狀況:
這三種狀況比較簡單,就放在一塊兒說明了,它們都不涉及旋轉,只涉及顏色翻轉,換句話說就是隻是結點合併無拆分。
狀況 1 和 2,不影響紅黑樹的性質,不會打破平衡,直接插入便可:
狀況 3,P 爲紅色(不是根結點),U 也是紅色,兩個樹插入狀況以下:
以 [7, 5, 9, 3] 輸入序列爲例,兩個樹構建過程以下:
狀況 4,P 爲紅色,而 U 爲黑色,此時,在 2-4 樹看來這個結點就是一個 3-結點,直接插入變成 4-結點;而對於紅黑樹,它爲了和這個 2-4 樹結構保持一致,會根據不一樣的狀況作旋轉,分別有如下四種可能:
相反的:
以 [7, 5, 9, 3, 4] 輸入序列爲例,也就是在上圖的基礎上,插入 4,演示 P 爲左,N 爲右,樹的旋轉過程:
其餘狀況,左右互換便可,可自行嘗試分析。這裏給出最開始提供的紅黑樹和 2-3-4 樹它們的動態構建過程,輸入序列爲 [7, 5, 9, 3, 4, 8, 10, 11, 12, 13, 14, 15],首先是 2-3-4 樹的構建:
紅黑樹構建時會有一次根結點調整,可注意一下:
二叉查找樹的結點無非是有兩個子結點,有一個子結點和葉子結點三種,其中有兩個子結點的 M 結點的刪除邏輯是:
因此,刪除任一結點的問題就簡化成了:刪除一個最多隻有一個孩子的結點的狀況,而且全部的刪除操做都在葉子結點完成,只不過刪除的結點再也不是一開始想刪除的結點,但結點的值最終是刪除了,而樹結構的變化與簡化問題相比,並不重要。
在分析紅黑樹的刪除以前,簡單來看下 2-3-4 樹的刪除狀況。
它相似二叉查找樹的刪除,實際的刪除操做也是在葉子結點完成,只不過在刪除的過程當中涉及到結點的合併,主要有 3 種不一樣的狀況:
上面這些狀況,有一個前提就是,在遍歷查找待刪除結點時,必須保證路過的結點都至少有 2 個 key,不是的話就須要合併結點。這點比較難理解,在插入時,會把遍歷過程當中遇到的4-結點 進行拆分,相對的,在刪除時,就要保證遍歷的結點至少有 2 個 key,也就至關於把以前拆分的進行了合併。
如下圖示演示了上述的每種可能的刪除狀況:
簡單來講,理解 2-3-4 樹刪除的重點就是:
紅黑樹的刪除也一樣相似二叉查找樹,不過要考慮平衡,也就是結點顏色問題,要麻煩一點。
首先聲明一點,接下來講的紅黑樹葉子結點和二叉查找樹葉子結點相同,若是要強調紅黑樹結點是空的葉子結點 NIL 會特殊說明,畫圖會使用黑色方框表示。
假設待刪除結點爲 M,若是有非葉子結點,稱爲 C,那麼有兩種比較簡單的刪除狀況:
注意:M 有且僅有一個非葉子的左或右孩子結點,至關於 2-3-4 樹刪除的狀況 1。
這兩個狀況,本質都是刪除了一個紅色結點,不影響總體平衡。以 [7, 5, 9, 3, 4] 輸入序列構建的紅黑樹爲例,演示以上兩種比較簡單的狀況:
刪除比較複雜的是 M 和 C 都是黑色的狀況,此時 M 確定是葉子節點,而 C 確定是 NIL 結點,若是不是這樣的狀況將違反性質5。
一個黑色結點被刪除會打破平衡,須要找一個結點填補這個空缺,假設待刪除結點爲 M,刪除後它的位置上就變成了 NIL 結點,爲了方便描述,這個結點記爲 N,P 表示 N 的父結點,S 表示 N 兄弟結點,S 若是存在左右孩子,分別使用 SL 和 SR 表示,那麼刪除就有如下幾種狀況:
刪除後,N 變成了根結點,也就是說刪除前只有 M 這一個結點,直接刪除便可。
S 是紅色,那麼它必有兩個孩子結點,且都爲黑色,並且 P 也確定是黑色。此時,交換 P 和 S 的顏色,而後對 P 左旋轉,以下:
如今,結點 N 的父結點變成了紅色,兄弟結點變成了 SL,此時就能夠按照狀況 四、五、6繼續處理。
P 是黑色,S 也是黑色,而且 S 也沒有非空的孩子結點。此時,直接將 S 變成紅色,那麼通過 S 的路徑也就少了一個黑色結點,總體上就致使通過 P 的路徑比原來少了一個黑色結點,把不平衡狀態從結點 N 轉移到告終點 P,能夠把 P 按 狀況1 處理,直到遇到根結點,以此造成遞歸:
P 是紅色,S 是黑色,而且 S 也沒有非空的孩子結點。此時,只要交換 P 和 S 的顏色,正好填補了少一個黑色結點的空缺,也就是恢復了平衡的狀態:
P 任意顏色,S 黑色,S 的左孩子紅色,(S 有右孩子也是紅色)。此時,對 S 右旋轉,並交換 S 和 SL 的顏色:
其實就是把這種狀況,轉成了 狀況 6 進行處理。
P 任意顏色,S 黑色,S 的右孩子紅色,(S 有左孩子也是紅色)。此時,對 P 左旋轉,交換 P 和 S 的顏色,並將 SR 變成黑色:
此時恢復平衡的狀態,不管 P 以前是什麼顏色,N 都比以前多了一個黑色父結點。假設 P 原先是紅色的,如今變成了黑色;假設原先是黑色的,如今 P 又多了一個黑色的父結點 S,因此,不管怎樣,通過結點 N 路徑增長了一個黑色結點。
以上 6 種狀況,結點 N 都是左孩子,若是是右孩子,只需把左右對調便可。類比 2-3-4 樹的刪除,理解黑色結點刪除後的關鍵就是:
最後來看下 2-3-4 樹和紅黑樹動態刪除的過程,輸入序列爲 [7, 5, 9, 3, 4, 8, 10, 11, 12, 13, 14, 15, 16],刪除順序是 [16, 13, 11, 7, 15, 14, 8, 4, 9, 10, 5, 3, 12],首先是 2-3-4 樹的動態刪除過程:
紅黑樹動態刪除的過程:
紅黑樹確實比較複雜,單純的分析性質和旋轉,意義不大,而 2-3-4 樹就不同了,它的插入和刪除簡單多了,而紅黑樹的旋轉和變色最終也是爲了和同構的 2-3-4 樹保持一致,本文就是相互結合分析,互相印證,相信會相對容易理解一點。
動圖來自網站:www.cs.usfca.edu/~galles/vis… 它支持單步調試,有興趣能夠試一下。
搜索公衆號「頓悟源碼」獲取更多源碼分析和造的輪子。