紅黑樹這個數據結構,讓你又愛又恨?看了這篇,妥妥的征服它

紅黑樹是一個比較複雜的數據結構,相信不少人也只知其名而不知其意,由於理解它的原理確實須要花費必定的功夫。之因此寫這篇文章,也是爲了更好的理解 Java 中 TreeMap 的源碼。html

寫以前,搜了下網上的文章,說實話,看完有點懵,大部分一上來就給你它的五大性質,而後就是一頓插入、刪除、旋轉操做,就完事了,理解起來至關吃力。node

本文將結合 2-3-4 樹,按部就班地介紹紅黑樹的由來原理,相信看完以後,你對它會有更清晰的認識。此外,這裏描述的是普通紅黑樹,而不是它的變體左傾紅黑樹(LLRB),這一點須要注意。算法

紅黑樹的引入

二叉查找樹

紅黑樹的由來要從二叉查找樹提及。二叉查找樹是一顆二叉樹,它每一個結點的值都大於其左子樹的任意結點而小於右子樹的任意結點,它結合了鏈表插入的靈活性有序數組查找的高效性(二分查找)編程

bst.png

對於使用二叉查找樹的算法,它的運行時間取決於樹的形狀,而樹的形狀又取決於結點插入的前後順序。如上圖所示,最好狀況下N 個結點的樹是徹底平衡的,每條空連接到根結點的距離都爲 ~lgN;而在最壞的狀況下,搜索路徑上可能有 N 個結點,退化成了鏈表數組

因此,爲了保證運行時間始終在對數級別,在動態構建二叉查找樹時,但願保持其平衡性,也就是下降樹的高度,使其儘量爲 ~lgN,這樣就能保證全部的查找都能在 ~lgN 次比較內結束,就像二分查找那樣,這樣的樹被稱爲平衡二叉查找樹數據結構

AVL 樹

第一個自平衡二叉查找樹就是AVL 樹,它規定,每一個結點的左右子樹的高度之差不超過 1。在插入或刪除結點,打破平衡後,就會經過一次或屢次樹旋轉來從新平衡。編程語言

AVL 樹是嚴格平衡的,適用於查找密集型應用程序,由於在頻繁插入或刪除結點的場景下,它花費在樹旋轉的代價過高。源碼分析

紅黑樹就是一種折中方案,它不追求完美平衡,只求部分達到平衡,從而下降在調整時樹旋轉次數。網站

2-3-4 樹

說到紅黑樹,就不得不提 2-3-4 樹,由於,紅黑樹能夠說就是它的一種特殊實現,對它有所瞭解,很是有助於理解紅黑樹。3d

保持平衡,無非是爲了下降樹的高度,若是把二叉查找樹通常化,容許一個結點保存多個值,變成多叉樹,也可認爲是下降了高度。

確切地說,標準二叉查找樹中的結點稱爲2-結點(一個值兩個子結點),如今引入3-結點(兩個值三個子結點)和4-結點(三個值四個子結點),這樣就能獲得一顆 2-3-4 樹(也稱爲 2-4 樹)。

node.png

2-3-4 樹是 4 階 B 樹,全部數據按排序順序保存,全部葉子結點都在相同的深度。對於大多數編程語言,直接實現 2-3-4 樹比較困難,而紅黑樹的實現相對要簡單容易,這也是紅黑樹應用普遍的一部分緣由。

2-3-4-rb.png

紅黑樹是二叉樹,全部的結點都是2-結點,因此爲了可以表示3-結點和4-結點,爲結點引入了顏色屬性

  • 黑色,表示普通結點
  • 紅色,表示可與父結點合併看做多值結點

如上圖所示,若是把紅黑樹的紅色結點和其父結點放平,它的結構就和左邊的 2-3-4 樹同樣。

紅黑樹

如今,來看下紅黑樹的性質:

  1. 每一個結點都是紅色或黑色的
  2. 根結點是黑色的**(是紅色最終也會轉黑色)**
  3. 全部葉子結點都是黑色的,這裏的葉子結點指的是空結點,經常使用 NIL 表示
  4. 若是結點爲紅色,則其子結點均爲黑色**(紅色表示可與父結點合併,子結點湊什麼熱鬧)**
  5. 從給定結點到其任何後代 NIL 結點的每條路徑都包含相同數量的黑色節點**(轉成 2-4 樹,全部葉子節點均在最底層)**

這些性質沒必要去背,就算記住後也絕對會忘,應該結合着 2-3-4 樹理解性記憶。

另外,紅黑樹中的旋轉顏色翻轉,就至關於 2-3-4 樹中的拆分合併,而且 2-3-4 樹結點的拆分和合並,理解起來至關簡單。對比分析和理解紅黑樹的操做,絕對讓你眼前一亮。

樹旋轉

在分析插入和刪除以前,先了解下什麼是樹旋轉。樹旋轉是二叉樹中調整子樹的一種操做,經常使用於調整樹的局部平衡性,它包含兩種方式,左旋轉右旋轉

rotate.png

其實旋轉操做很容易理解:左旋轉就是將用兩個結點中的較小者做爲根結點變爲將較大者做爲根結點,右旋轉恰好於此相反,如上圖所示:

  • 右旋轉,就是將較小者 L 做爲根結點,而後調整 L 和 P 的子樹
  • 左旋轉,就是將較大者 P 做爲根結點,而後調整 P 和 L 的子樹

紅黑樹的旋轉其實就是爲了確保和其結構相同的 2-3-4 樹的一一對應關係,同時保證紅黑樹的有序性和平衡性。

插入

接下來,就結合 2-4 樹分析結點的插入,首先 2-4 樹的插入邏輯是這樣的:

  • 若是是 2-結點,直接插入變成 3-結點
  • 若是是 3-結點,直接插入變成 4-結點
  • 若是是 4-結點,首先進行分裂,變成 2-結點,再插入

2-4 樹插入的都是葉子結點紅黑樹插入的結點都是紅色的,由於在 2-4 樹中,待插入結點都認爲能夠插入到一個多值結點中。

這裏假設待插入結點爲 NPN 的父結點,GN 的祖父結點,UN 的叔叔結點(即父結點的兄弟結點),那麼紅黑樹有如下幾種插入狀況:

  1. N 是根結點,即紅黑樹的第一個結點
  2. N 的父結點(P)爲黑色
  3. P紅色的(不是根結點),它的兄弟結點 U 也是紅色
  4. P紅色,而 U黑色

狀況 1,2,3

這三種狀況比較簡單,就放在一塊兒說明了,它們都不涉及旋轉只涉及顏色翻轉,換句話說就是隻是結點合併無拆分

狀況 1 和 2,不影響紅黑樹的性質,不會打破平衡,直接插入便可:

  • 對於 2-4 樹來講,空樹插入就是一個 2-結點->3-結點->4-結點轉換的過程
  • 紅黑樹就是創建一個根結點爲黑色的標準 2-結點

狀況 3P紅色(不是根結點),U 也是紅色,兩個樹插入狀況以下:

  • 在 2-4 樹中,就意味着這是個 4-結點,它首先拆分紅 2-結點,而後再進行插入
  • 對於紅黑樹,它至關於已經拆分,直接變色即:PU 變成黑色,G 變成紅色,若 G根結點,直接變黑,不然遞歸向上檢查是否形成不平衡

[7, 5, 9, 3] 輸入序列爲例,兩個樹構建過程以下:

case-1-2-3.png

狀況 4

狀況 4P紅色,而 U黑色,此時,在 2-4 樹看來這個結點就是一個 3-結點,直接插入變成 4-結點;而對於紅黑樹,它爲了和這個 2-4 樹結構保持一致,會根據不一樣的狀況作旋轉,分別有如下四種可能:

  • PG左孩子,若 NP左孩子,那麼將祖父結點 G 右旋轉 便可
  • PG左孩子,若 NP右孩子,那麼 P 先左旋轉,而後再將祖父結點 G 右旋轉

相反的:

  • PG右孩子,若 NP右孩子,那麼將祖父結點 G 左旋轉 便可
  • PG右孩子,若 NP左孩子,那麼 P 先右旋轉,而後再將祖父結點 G 左旋轉

[7, 5, 9, 3, 4] 輸入序列爲例,也就是在上圖的基礎上,插入 4,演示 P 爲左,N 爲右,樹的旋轉過程:

case-4.png

其餘狀況,左右互換便可,可自行嘗試分析。這裏給出最開始提供的紅黑樹2-3-4 樹它們的動態構建過程,輸入序列爲 [7, 5, 9, 3, 4, 8, 10, 11, 12, 13, 14, 15],首先是 2-3-4 樹的構建:

2-3-4-btree.gif

紅黑樹構建時會有一次根結點調整,可注意一下:

rbtree-20.gif

刪除

二叉查找樹的結點無非是有兩個子結點有一個子結點葉子結點三種,其中有兩個子結點M 結點的刪除邏輯是:

  1. 首先尋找 M 結點左子樹最大右子樹最小的結點 X
  2. 而後把 X 結點的值複製到 M 結點
  3. 最後刪除 X 結點,而這個結點要麼是葉子結點,要麼就只有一個孩子

因此,刪除任一結點的問題就簡化成了:刪除一個最多隻有一個孩子的結點的狀況,而且全部的刪除操做都在葉子結點完成,只不過刪除的結點再也不是一開始想刪除的結點,但結點的值最終是刪除了,而樹結構的變化與簡化問題相比,並不重要。

在分析紅黑樹的刪除以前,簡單來看下 2-3-4 樹的刪除狀況。

2-3-4 樹結點刪除

它相似二叉查找樹的刪除,實際的刪除操做也是在葉子結點完成,只不過在刪除的過程當中涉及到結點的合併,主要有 3 種不一樣的狀況:

  1. 若是元素 K 是內部結點,而且在一個至少有 2 個 key 的多值葉子結點內部,則只需從結點中刪除 K
  2. 若是元素 K 是內部結點,且有左孩子和右孩子,那麼: 2.1 若是左孩子至少有 2 個 key,那麼找一個最大值替換 K,而後刪除這個最大值 2.2 若是右孩子至少有 2 個 key,那麼找一個最小值替換 K,而後刪除這個最小值 2.3 若是兩個孩子都只有 1 個 key,那麼將 K 下沉,與其子女合併,造成一個至少有 2 個 key 的結點,最後再刪除 K
  3. 若是元素 K 不是內部結點,所在結點只有它 1 個 key,那麼根據如下狀況,最終會轉成狀況1狀況2: 3.1 若是它的兄弟結點至少有 2 個 key,那麼選擇一個推到父節點中,再把舊的父節點下沉和 K 合併 3.2 若是它的兄弟結點也只有 1 個 key,那麼將父結點下沉,與其子女合併,再刪除 K因此,此時須要父結點至少有 2 個 key,若是沒有那麼在父結點上遞歸按狀況 3 處理

上面這些狀況,有一個前提就是,在遍歷查找待刪除結點時,必須保證路過的結點都至少有 2 個 key,不是的話就須要合併結點。這點比較難理解,在插入時,會把遍歷過程當中遇到的4-結點 進行拆分,相對的,在刪除時,就要保證遍歷的結點至少有 2 個 key,也就至關於把以前拆分的進行了合併

如下圖示演示了上述的每種可能的刪除狀況:

2-3-4-delete.png

簡單來講,理解 2-3-4 樹刪除的重點就是:

  • 若是刪除的結點是多值結點,直接刪除便可
  • 不然從兄弟結點獲取一個多餘的結點填補空缺
  • 再不然就從父結點獲取一個結點填補空缺,若是父結點沒有多餘結點,將問題遞歸到父結點處理。

紅黑樹的刪除

紅黑樹的刪除也一樣相似二叉查找樹,不過要考慮平衡,也就是結點顏色問題,要麻煩一點。

首先聲明一點,接下來講的紅黑樹葉子結點二叉查找樹葉子結點相同,若是要強調紅黑樹結點是空的葉子結點 NIL 會特殊說明,畫圖會使用黑色方框表示。

假設待刪除結點爲 M,若是有非葉子結點,稱爲 C,那麼有兩種比較簡單的刪除狀況:

  1. M 爲紅色結點,那麼它必是葉子結點,直接刪除便可,由於若是它有一個黑色的非葉子結點,那麼就違反了性質5,經過 M 向左或向右的路徑黑色結點不等
  2. M 是黑色而 C 是紅色,只須要讓 C 替換到 M 的位置,並變成黑色便可,或者說交換 CM 的值,並刪除 C(就是第一個簡單的狀況)。

注意M 有且僅有一個非葉子的左或右孩子結點,至關於 2-3-4 樹刪除的狀況 1

這兩個狀況,本質都是刪除了一個紅色結點,不影響總體平衡。以 [7, 5, 9, 3, 4] 輸入序列構建的紅黑樹爲例,演示以上兩種比較簡單的狀況:

delete-1.png

刪除比較複雜的是 MC 都是黑色的狀況,此時 M 確定是葉子節點,而 C 確定是 NIL 結點,若是不是這樣的狀況將違反性質5

一個黑色結點被刪除會打破平衡,須要找一個結點填補這個空缺,假設待刪除結點爲 M,刪除後它的位置上就變成了 NIL 結點,爲了方便描述,這個結點記爲 NP 表示 N 的父結點,S 表示 N 兄弟結點,S 若是存在左右孩子,分別使用 SLSR 表示,那麼刪除就有如下幾種狀況:

delete-2-case.png

狀況 1 - N 是根結點

刪除後,N 變成了根結點,也就是說刪除前只有 M 這一個結點,直接刪除便可。

狀況 2 - P 黑 S 紅

S 是紅色,那麼它必有兩個孩子結點,且都爲黑色,並且 P 也確定是黑色。此時,交換 PS 的顏色,而後對 P 左旋轉,以下:

delete-2-2-case.png

如今,結點 N 的父結點變成了紅色,兄弟結點變成了 SL,此時就能夠按照狀況 四、五、6繼續處理。

狀況 3 - P 黑 S 黑

P 是黑色,S 也是黑色,而且 S 也沒有非空的孩子結點。此時,直接將 S 變成紅色,那麼通過 S 的路徑也就少了一個黑色結點,總體上就致使通過 P 的路徑比原來少了一個黑色結點,把不平衡狀態從結點 N 轉移到告終點 P,能夠把 P狀況1 處理,直到遇到根結點,以此造成遞歸

delete-2-3-case.png

狀況 4 - P 紅 S 黑

P 是紅色,S 是黑色,而且 S 也沒有非空的孩子結點。此時,只要交換 PS 的顏色,正好填補了少一個黑色結點的空缺,也就是恢復了平衡的狀態:

delete-2-4-case.png

狀況 5 - P 任意 S 黑 SL 紅

P 任意顏色,S 黑色,S 的左孩子紅色,(S 有右孩子也是紅色)。此時,對 S 右旋轉,並交換 SSL 的顏色:

delete-2-5-case.png

其實就是把這種狀況,轉成了 狀況 6 進行處理。

狀況 6 - P 任意 S 黑,SR 紅

P 任意顏色,S 黑色,S 的右孩子紅色,(S 有左孩子也是紅色)。此時,對 P 左旋轉,交換 PS 的顏色,並將 SR 變成黑色:

delete-2-6-case.png

此時恢復平衡的狀態,不管 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-delete.gif

紅黑樹動態刪除的過程:

rb-tree-delete.gif

小結

紅黑樹確實比較複雜,單純的分析性質和旋轉,意義不大,而 2-3-4 樹就不同了,它的插入和刪除簡單多了,而紅黑樹的旋轉和變色最終也是爲了和同構的 2-3-4 樹保持一致,本文就是相互結合分析,互相印證,相信會相對容易理解一點。

動圖來自網站:www.cs.usfca.edu/~galles/vis… 它支持單步調試,有興趣能夠試一下。

搜索公衆號「頓悟源碼」獲取更多源碼分析和造的輪子。

相關文章
相關標籤/搜索