閱讀文本大概須要3分鐘。java
先推薦一門算法課程,9.9投資本身不吃虧。
1.紅黑樹簡介
紅黑樹是一種自平衡的二叉查找樹,是一種高效的查找樹。它是由 Rudolf Bayer 於1978年發明,在當時被稱爲對稱二叉 B 樹(symmetric binary B-trees)
。後來,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改成現在的紅黑樹
。紅黑樹具備良好的效率,它可在 O(logN)
時間內完成查找、增長、刪除等操做。所以,紅黑樹在業界應用很普遍,好比 Java 中的 TreeMap,JDK 1.8 中的 HashMap、C++ STL 中的 map 均是基於紅黑樹結構實現的。考慮到紅黑樹是一種被普遍應用的數據結構,因此咱們頗有必要去弄懂它。git
2.紅黑樹的性質
學過二叉查找樹的同窗都知道,普通的二叉查找樹在極端狀況下可退化成鏈表,此時的增刪查效率都會比較低下。爲了不這種狀況,就出現了一些自平衡的查找樹,好比 AVL,紅黑樹等。這些自平衡的查找樹經過定義一些性質,將任意節點的左右子樹高度差控制在規定範圍內,以達到平衡狀態。以紅黑樹爲例,紅黑樹經過以下的性質定義實現自平衡:github
節點是紅色或黑色。web
根是黑色。算法
全部葉子都是黑色(葉子是NIL節點)。spring
每一個紅色節點必須有兩個黑色的子節點。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點。)微信
從任一節點到其每一個葉子的全部簡單路徑都包含相同數目的黑色節點(簡稱黑高)。數據結構
有了上面的幾個性質做爲限制,便可避免二叉查找樹退化成單鏈表的狀況。可是,僅僅避免這種狀況還不夠,這裏還要考慮某個節點到其每一個葉子節點路徑長度的問題。若是某些路徑長度過長,那麼,在對這些路徑上的及誒單進行增刪查操做時,效率也會大大下降。這個時候性質4和性質5用途就凸顯了,有了這兩個性質做爲約束,便可保證任意節點到其每一個葉子節點路徑最長不會超過最短路徑的2倍。緣由以下:架構
當某條路徑最短時,這條路徑必然都是由黑色節點構成。當某條路徑長度最長時,這條路徑必然是由紅色和黑色節點相間構成(性質4限定了不能出現兩個連續的紅色節點)。而性質5又限定了從任一節點到其每一個葉子節點的全部路徑必須包含相同數量的黑色節點。此時,在路徑最長的狀況下,路徑上紅色節點數量 = 黑色節點數量。該路徑長度爲兩倍黑色節點數量,也就是最短路徑長度的2倍。舉例說明一下,請看下圖:app
上圖畫出了從根節點 M 出發的到其葉子節點的最長和最短路徑。這裏偷懶只畫出了兩條最長路徑,實際上最長路徑有4條,分別爲:
M -> Q -> O -> N
M -> Q -> O -> p
M -> Q -> Y -> X
M -> Q -> Y -> Z
長度爲4,最短路徑爲 M -> E
,長度爲2。最長路徑的長度正好爲最短路徑長度的2倍。
前面說了關於紅黑樹的一些性質,這裏還須要補充一些其餘方面的東西。在紅黑樹簡介一節中說到紅黑樹被髮明出來的時候並不叫紅黑樹
,而是叫作對稱二叉 B 樹
,從名字中可發現紅黑樹和 B 樹(這裏指的是2-3樹)或許有必定的關聯,事實也正是如此。若是對紅黑樹的性質稍加修改,就能讓紅黑樹和B樹造成一一對應的關係。關於紅黑樹和 B 樹關係的細節這裏不展開說明了,有興趣的同窗能夠參考《算法》第4版,那本書上講的很透徹。
3.紅黑樹操做
紅黑樹的基本操做和其餘樹形結構同樣,通常都包括查找、插入、刪除等操做。前面說到,紅黑樹是一種自平衡的二叉查找樹,既然是二叉查找樹的一種,那麼查找過程和二叉查找樹同樣,比較簡單,這裏再也不贅述。相對於查找操做,紅黑樹的插入和刪除操做就要複雜的多。尤爲是刪除操做,要處理的狀況比較多,不過你們若是靜下心來去看,會發現其實也沒想的那麼難。好了,廢話就說到這,接下來步入正題吧。
3.1 旋轉操做
在分析插入和刪除操做前,這裏須要插個隊,先說明一下旋轉操做,這個操做在後續操做中都會用獲得。旋轉操做分爲左旋和右旋,左旋是將某個節點旋轉爲其右孩子的左孩子,而右旋是節點旋轉爲其左孩子的右孩子。這話聽起來有點繞,因此仍是請看下圖:
上圖包含了左旋和右旋的示意圖,這裏以右旋爲例進行說明,右旋節點 M 的步驟以下:
將節點 M 的左孩子引用指向節點 E 的右孩子
將節點 E 的右孩子引用指向節點 M,完成旋轉
上面分析了右旋操做,左旋操做與此相似,你們有興趣本身畫圖試試吧,這裏再也不贅述了。旋轉操做自己並不複雜,這裏先分析到這吧。
3.2 插入
紅黑樹的插入過程和二叉查找樹插入過程基本相似,不一樣的地方在於,紅黑樹插入新節點後,須要進行調整,以知足紅黑樹的性質。性質1規定紅黑樹節點的顏色要麼是紅色要麼是黑色,那麼在插入新節點時,這個節點應該是紅色仍是黑色呢?答案是紅色,緣由也不難理解。若是插入的節點是黑色,那麼這個節點所在路徑比其餘路徑多出一個黑色節點,這個調整起來會比較麻煩(參考紅黑樹的刪除操做,就知道爲啥多一個或少一個黑色節點時,調整起來這麼麻煩了)。若是插入的節點是紅色,此時全部路徑上的黑色節點數量不變,僅可能會出現兩個連續的紅色節點的狀況。這種狀況下,經過變色和旋轉進行調整便可,比以前的簡單多了。
接下來,將分析插入紅色節點後紅黑樹的狀況。這裏假設要插入的節點爲 N,N 的父節點爲 P,祖父節點爲 G,叔叔節點爲 U。插入紅色節點後,會出現5種狀況,分別以下:
3.2.1 狀況一
插入的新節點 N 是紅黑樹的根節點,這種狀況下,咱們把節點 N 的顏色由紅色變爲黑色,性質2(根是黑色)被知足。同時 N 被染成黑色後,紅黑樹全部路徑上的黑色節點數量增長一個,性質5(從任一節點到其每一個葉子的全部簡單路徑都包含相同數目的黑色節點)仍然被知足。
3.2.2 狀況二
N 的父節點是黑色,這種狀況下,性質4(每一個紅色節點必須有兩個黑色的子節點)和性質5沒有受到影響,不須要調整。
3.2.3 狀況三
N 的父節點是紅色(節點 P 爲紅色,其父節點必然爲黑色),叔叔節點 U 也是紅色。因爲 P 和 N 均爲紅色,全部性質4被打破,此時須要進行調整。這種狀況下,先將 P 和 U 的顏色染成黑色,再將 G 的顏色染成紅色。此時通過 G 的路徑上的黑色節點數量不變,性質5仍然知足。但須要注意的是 G 被染成紅色後,可能會和它的父節點造成連續的紅色節點,此時須要遞歸向上調整。
3.2.4 狀況四
N 的父節點爲紅色,叔叔節點爲黑色。節點 N 是 P 的右孩子,且節點 P 是 G 的左孩子。此時先對節點 P 進行左旋,調整 N 與 P 的位置。接下來按照狀況五進行處理,以恢復性質4。
3.2.5 狀況五
N 的父節點爲紅色,叔叔節點爲黑色。N 是 P 的左孩子,且節點 P 是 G 的左孩子。此時對 G 進行右旋,調整 P 和 G 的位置,並互換顏色。通過這樣的調整後,性質4被恢復,同時也未破壞性質5。
3.2.6 插入總結
上面五種狀況中,狀況一和狀況二比較簡單,狀況3、4、五稍複雜。但若是細心觀察,會發現這三種狀況的區別在於叔叔節點的顏色,若是叔叔節點爲紅色,直接變色便可。若是叔叔節點爲黑色,則須要選選擇,再交換顏色。當把這三種狀況的圖畫在一塊兒就區別就比較容易觀察了,以下圖:
3.3 刪除
相較於插入操做,紅黑樹的刪除操做則要更爲複雜一些。刪除操做首先要肯定待刪除節點有幾個孩子,若是有兩個孩子,不能直接刪除該節點。而是要先找到該節點的前驅(該節點左子樹中最大的節點)或者後繼(該節點右子樹中最小的節點),而後將前驅或者後繼的值複製到要刪除的節點中,最後再將前驅或後繼刪除。因爲前驅和後繼至多隻有一個孩子節點,這樣咱們就把原來要刪除的節點有兩個孩子的問題轉化爲只有一個孩子節點的問題,問題被簡化了一些。咱們並不關心最終被刪除的節點是不是咱們開始想要刪除的那個節點,只要節點裏的值最終被刪除就好了,至於樹結構如何變化,這個並不重要。
紅黑樹刪除操做的複雜度在於刪除節點的顏色,當刪除的節點是紅色時,直接拿其孩子節點補空位便可。由於刪除紅色節點,性質5(從任一節點到其每一個葉子的全部簡單路徑都包含相同數目的黑色節點)仍可以被知足。當刪除的節點是黑色時,那麼全部通過該節點的路徑上的黑節點數量少了一個,破壞了性質5。若是該節點的孩子爲紅色,直接拿孩子節點替換被刪除的節點,並將孩子節點染成黑色,便可恢復性質5。但若是孩子節點爲黑色,處理起來就要複雜的多。分爲6種狀況,下面會展開說明。
在展開說明以前,咱們先作一些假設,方便說明。這裏假設最終被刪除的節點爲X
(至多隻有一個孩子節點),其孩子節點爲N
,X
的兄弟節點爲S
,S
的左節點爲 SL,右節點爲 SR。接下來討論是創建在節點 X
被刪除,節點 N
替換X
的基礎上進行的。這裏說明把被刪除的節點X
特意拎出來講一下的緣由是防止你們誤覺得節點N
會被刪除,否則後面就會看不明白。
在上面的基礎上,接下來就能夠展開討論了。紅黑樹刪除有6種狀況,分別是:
3.3.1 狀況一
N 是新的根。在這種情形下,咱們就作完了。咱們從全部路徑去除了一個黑色節點,而新根是黑色的,因此性質都保持着。
上面是維基百科中關於紅黑樹刪除的狀況一說明,因爲沒有配圖,看的有點暈。通過思考,我以爲可能會是下面這種情形:
要刪除的節點 X 是根節點,且左右孩子節點均爲空節點,此時將節點 X 用空節點替換完成刪除操做。
可能還有其餘情形,你們若是知道,煩請告知。
3.3.2 狀況二
S 爲紅色,其餘節點爲黑色。這種狀況下能夠對 N 的父節點進行左旋操做,而後互換 P 與 S 顏色。但這並未結束,通過節點 P 和 N 的路徑刪除前有3個黑色節點(P -> X -> N
),如今只剩兩個了(P -> N
)。比未通過 N 的路徑少一個黑色節點,性質5仍不知足,還須要繼續調整。不過此時能夠按照狀況4、5、六進行調整。
3.3.2 狀況三
N 的父節點,兄弟節點 S 和 S 的孩子節點均爲黑色。這種狀況下能夠簡單的把 S 染成紅色,全部通過 S 的路徑比以前少了一個黑色節點,這樣通過 N 的路徑和通過 S 的路徑黑色節點數量一致了。但通過 P 的路徑比不通過 P 的路徑少一個黑色節點,此時須要從狀況一開始對 P 進行平衡處理。
3.3.4 狀況四
N 的父節點爲紅色,叔叔節點爲黑色。節點 N 是 P 的右孩子,且節點 P 是 G 的左孩子。此時先對節點 P 進行左旋,調整 N 與 P 的位置。接下來按照狀況五進行處理,以恢復性質4。
這裏須要特別說明一下,上圖中的節點 N 並不是是新插入的節點。當 P 爲紅色時,P 有兩個孩子節點,且孩子節點均爲黑色,這樣從 G 出發到各葉子節點路徑上的黑色節點數量才能保持一致。既然 P 已經有兩個孩子了,因此 N 不是新插入的節點。狀況四是由以 N 爲根節點的子樹中插入了新節點,通過調整後,致使 N 被變爲紅色,進而致使了狀況四的出現。考慮下面這種狀況(PR 節點就是上圖的 N 節點):
如上圖,插入節點 N 並按狀況三處理。此時 PR 被染成了紅色,與 P 節點造成了連續的紅色節點,這個時候就需按狀況四再次進行調整。
3.3.5 狀況五
S 爲黑色,S 的左孩子爲紅色,右孩子爲黑色。N 的父節點顏色可紅可黑,且 N 是 P 左孩子。這種狀況下對 S 進行右旋操做,並互換 S 和 SL 的顏色。此時,全部路徑上的黑色數量仍然相等,N 兄弟節點的由 S 變爲了 SL,而 SL 的右孩子變爲紅色。接下來咱們到狀況六繼續分析。
3.3.6 狀況六
S 爲黑色,S 的右孩子爲紅色。N 的父節點顏色可紅可黑,且 N 是其父節點左孩子。這種狀況下,咱們對 P 進行左旋操做,並互換 P 和 S 的顏色,並將 SR 變爲黑色。由於 P 變爲黑色,因此通過 N 的路徑多了一個黑色節點,通過 N 的路徑上的黑色節點與刪除前的數量一致。對於不通過 N 的路徑,則有如下兩種狀況:
該路徑通過 N 新的兄弟節點 SL ,那它以前必然通過 S 和 P。而 S 和 P 如今只是交換顏色,對於通過 SL 的路徑不影響。
該路徑通過 N 新的叔叔節點 SR,那它以前必然通過 P、 S 和 SR,而如今它只通過 S 和 SR。在對 P 進行左旋,並與 S 換色後,通過 SR 的路徑少了一個黑色節點,性質5被打破。另外,因爲 S 的顏色可紅可黑,若是 S 是紅色的話,會與 SR 造成連續的紅色節點,打破性質4(每一個紅色節點必須有兩個黑色的子節點)。此時僅需將 SR 由紅色變爲黑色便可同時恢復性質4和性質5(從任一節點到其每一個葉子的全部簡單路徑都包含相同數目的黑色節點。)。
3.3.7 刪除總結
紅黑樹刪除的狀況比較多,你們剛開始看的時候可能會比較暈。可能會產生這樣的疑問,爲啥紅黑樹會有這種刪除狀況,爲啥又會有另外一種狀況,它們之間有什麼聯繫和區別?和你們同樣,我剛開始看的時候也有這樣的困惑,直到我把全部狀況對應的圖形畫在一塊兒時,撥雲見日,一切都明瞭了。此時天空中出現了4個字,原來如此、原來如此、原來如此。因此,請看圖吧:
4.總結
紅黑樹是一種重要的二叉樹,應用普遍,但在不少數據結構相關的書本中出現的次數並很少。不少書中要麼不說,要麼就一筆帶過,並不會進行詳細的分析,這多是由於紅黑樹比較複雜的緣故。我在學習紅黑樹的時候也找了不少資料,但整體感受講的都不太好。尤爲是在我學習刪除操做的時候,不少資料是實在人看不下去,看的我很痛苦。直到我看到維基百科上關於紅黑樹的分析時,非常欣喜。這篇文章分析的頗有條理,言簡意賅,比不少資料好了太多。本文對紅黑樹的分析也主要參考了維基百科中的紅黑樹分析,並對維基百科中容易讓人產生疑問和誤解的地方進行了說明。同時維基百科中文版紅黑樹文中的圖片較爲模糊,這裏我從新進行了繪製。須要說明的是,維基百科中文版沒法打開了,文中關於維基百科的連接都是英文版的。另外在給你們推薦一個數據結構可視化的網站,裏面包含常見數據結構可視化過程,地址爲:t.cn/RZFgryr。
另外,因爲紅黑樹自己比較複雜,實現也較爲複雜。在寫這篇文章以前,我曾嘗試過用 Java 語言實現紅黑樹的增刪操做,最終只寫出了新增節點操做,刪除沒作出來。並且本身寫的新增邏輯實在太繁瑣,寫的很差看,無法拿出來 show。因此最後把 Java 中的 TreeMap 增刪相關源碼拷出來,按照本身的需求把源碼修改了一下,也勉強算是實現了紅黑樹吧。代碼放到了 github 上,傳送門 -> RBTree.java。
最後,若是你也在學習紅黑樹,但願這篇文章可以幫助到你。另外,因爲紅黑樹自己比較複雜,加之本人水平有限,不免會出一些錯誤。若是有錯,還望你們指出來,咱們共同討論。
source: http://www.tianxiaobo.com/2018/01/11/紅黑樹詳細分析/
☆
往期精彩
☆
02 Nacos源碼編譯
關注我
天天進步一點點
本文分享自微信公衆號 - JAVA樂園(happyhuangjinjin88)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。