紅黑樹是數據結構中比較複雜的一種,最近與它交集頗多,因而花了一週的空閒時間跟它死磕,終於弄明白並實現了紅黑樹。寫文總結一下,但願能給試圖理解紅黑樹的同窗一些靈感,也讓我能記得更深入。html
在研究紅黑樹時吃了很多苦頭,緣由有二:git
雙黑、caseN
法,而插入和刪除的狀況不少,每種都有對應的處理方式,若是死記硬背的話,再過一段時間再回憶各類狀況可能就一頭霧水了。網絡上講紅黑樹的實現多來源於《算法導論》一書,直接講紅黑樹的實現,須要處理顏色和高度兩種屬性約束,比較晦澀。本文經過紅黑樹的等同—— 2-3-4樹,避開顏色屬性約束,也弱化了高度的影響,以另外一種方式去理解紅黑樹,雖然並不能徹底下降它的複雜度,但自認爲較之廣泛實現,更易記一些。github
文章最前面先放上紅黑樹的實現源碼,代碼在 Github 上,一開始實現時使用我最熟練的 PHP,後續添加了 Java 版,代碼均可以直接運行。源碼連接:GitHub-枕邊書-RBTree
,歡迎star
。算法
文章歡迎轉載,請註明出處:http://www.cnblogs.com/zhenbianshu/p/8185345.html。編程
紅黑樹是一種結點帶有顏色屬性的二叉查找樹,但它在二叉查找樹以外,還有如下要求:網絡
下圖就是一個典型的紅黑樹:數據結構
但實現上我省略了其中的 Nil 結點,通常以下圖,你們理解時也能夠忽略它們。編程語言
咱們知道二叉查找樹在不停地添加或刪除結點後,可能會致使結點狀況以下:優化
這種狀況下,二叉查找樹的查找效率最壞會下降爲 O(n)
。debug
而紅黑樹因爲在插入和刪除結點時都會進行變色旋轉等操做,在符合紅黑樹條件的狀況下,即便一邊子樹全是黑色結點,另外一邊子樹全是紅黑相間,兩子樹的高度差也不會超過一半。一棵有 n 個結點的紅黑樹高度至多爲 2log(n+1)
,查找效率最壞爲 O(log(n))
。
因此紅黑樹常被用於需求查找效率穩定的場景,如 Linux 中內核使用它管理內存區域對象、Java8 中 HashMap 的實現等,因此瞭解紅黑樹也頗有意義。
下面介紹一下紅黑樹的等同 2-3-4樹。
2-3-4樹是四階的 B樹(Balance Tree),它的結構有如下限制:
節點只能是 2-節點、3-節點、4-節點之一。
元素始終保持排序順序,總體上保持二叉查找樹的性質,即父結點大於左子結點,小於右子結點;並且結點有多個元素時,每一個元素必須大於它左邊的和它的左子樹中元素。
下圖是一個典型的 2-3-4樹(來自維基百科):
2-3-4樹的查詢操做像普通的二叉搜索樹同樣,很是簡單,但因爲其結點元素數不肯定,在一些編程語言中實現起來並不方便,實現通常使用它的等同——紅黑樹。
至於爲何說紅黑樹是 2-3-4樹的一種等同呢,這是由於 2-3-4樹的每個結點都對應紅黑樹的一種結構,因此每一棵 2-3-4樹也都對應一棵紅黑樹,下圖是 2-3-4樹不一樣結點與紅黑樹子樹的對應。
而上文中的 2-3-4樹也能夠轉換成一棵紅黑樹:
由紅黑樹的性質5,和 2-3-4樹的性質1,爲了便於理解紅黑樹和 2-3-4樹的對應關係,咱們能夠把紅黑樹從根結點到葉子結點的黑色結點個數定義爲高度
。
紅黑樹和 2-3-4樹的結點添加和刪除都有一個基本規則:避免子樹高度變化,由於不管是 2-3-4樹仍是紅黑樹,一旦子樹高度有變更,勢必會影響其餘子樹進行調整,因此咱們在插入和刪除結點時儘可能經過子樹內部調整來達到平衡,2-3-4樹實現平衡是經過結點的旋轉和結點元素數變化,紅黑樹是經過結點旋轉和變色。
下面來對照着 2-3-4樹說一下紅黑樹結點的添加和刪除:
2-3-4樹中結點添加須要遵照如下規則:
而將這些規則對應到紅黑樹裏,就是:
紅色
,這樣纔可能不會對紅黑樹的高度產生影響。黑+紅
子樹,插入後將其修復成 紅+黑+紅
子樹(對應 3-結點升元);紅+黑+紅
子樹,插入後將其修復成紅色祖父+黑色父叔+紅色孩子
子樹,而後再把祖父結點當成新插入的紅色結點遞歸向上層修復,直至修復成功或遇到 root 結點;如上圖所示,雖然向紅黑樹中插入了一個新結點,但因爲旋轉和變色,子樹的高度保持不變。
紅黑樹的刪除要比插入要複雜一些,咱們仍是類比 2-3-4樹來說:
將這些規則對應到紅黑樹中即:
替代結點
(左子樹的最右結點或右子樹的最左結點都能保證替換後保證二叉查找樹的結點的排序性質,葉子結點的替代結點是自身)替換掉被刪除結點,從替代的葉子結點向上遞歸修復;替代結點爲黑色(對應 2-3-4樹中 2-結點)時,意味着替代結點所在的子樹會降一層,須要依次檢驗如下三項,以恢復子樹高度:
父結點看成替代結點
遞歸向上處理。如上圖,刪除的要點是 找到替代結點
,若是替代結點是黑色,遞歸向上依次判斷侄子結點、父結點是否能夠補充被刪除的黑色,總體思想就是將刪除一個黑色結點形成的影響侷限在子樹內處理。
固然實現過程當中調試也佔了很大一部分,我使用了兩項方法幫助調試:
debug
屬性,用二分法設置此屬性來找到問題結點;printTree()
方法,實時打印樹結構,肯定代碼問題再分析;因爲紅黑樹相對其餘樹實在較爲複雜,只經過思考就徹底理解不太現實,還須要本身去試着畫,試着實現,我畫了 5 張 A4 紙的正反面纔算理解了紅黑樹,即使如此,在寫這篇文章時還發現了代碼中的可優化點。
並且代碼實現比畫圖還略複雜,理論中的一個旋轉
就包含了 左旋/右旋/先左旋再右旋/先右旋再左旋
幾種狀況,雖然有必定規律,仍是本身實現一下印象最深入。
關於本文有什麼問題能夠在下面留言交流,若是您以爲本文對您有幫助,能夠點擊下面的 推薦
支持一下我,博客一直在更新,歡迎 關注
。