經過2-3-4樹理解紅黑樹

前言

紅黑樹是數據結構中比較複雜的一種,最近與它交集頗多,因而花了一週的空閒時間跟它死磕,終於弄明白並實現了紅黑樹。寫文總結一下,但願能給試圖理解紅黑樹的同窗一些靈感,也讓我能記得更深入。html

在研究紅黑樹時吃了很多苦頭,緣由有二:git

  • 紅黑樹的插入和刪除很是複雜,不少人並無理解或徹底實現,或實現了的沒有任何註釋,讓人很難參考;
  • 網絡上紅黑樹的理解方式較爲單一,通常是 雙黑、caseN 法,而插入和刪除的狀況不少,每種都有對應的處理方式,若是死記硬背的話,再過一段時間再回憶各類狀況可能就一頭霧水了。

網絡上講紅黑樹的實現多來源於《算法導論》一書,直接講紅黑樹的實現,須要處理顏色和高度兩種屬性約束,比較晦澀。本文經過紅黑樹的等同—— 2-3-4樹,避開顏色屬性約束,也弱化了高度的影響,以另外一種方式去理解紅黑樹,雖然並不能徹底下降它的複雜度,但自認爲較之廣泛實現,更易記一些。github

文章最前面先放上紅黑樹的實現源碼,代碼在 Github 上,一開始實現時使用我最熟練的 PHP,後續添加了 Java 版,代碼均可以直接運行。源碼連接:GitHub-枕邊書-RBTree,歡迎star算法

文章歡迎轉載,請註明出處:http://www.cnblogs.com/zhenbianshu/p/8185345.html。編程


紅黑樹

定義

紅黑樹是一種結點帶有顏色屬性的二叉查找樹,但它在二叉查找樹以外,還有如下要求:網絡

  1. 節點是紅色或黑色。
  2. 根是黑色。
  3. 全部葉子都是黑色(葉子是NIL節點)。
  4. 每一個紅色節點必須有兩個黑色的子節點。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點。)
  5. 從任一節點到其每一個葉子的全部簡單路徑都包含相同數目的黑色節點。

下圖就是一個典型的紅黑樹:數據結構

但實現上我省略了其中的 Nil 結點,通常以下圖,你們理解時也能夠忽略它們。編程語言

優點和用途

咱們知道二叉查找樹在不停地添加或刪除結點後,可能會致使結點狀況以下:優化

這種狀況下,二叉查找樹的查找效率最壞會下降爲 O(n)debug

而紅黑樹因爲在插入和刪除結點時都會進行變色旋轉等操做,在符合紅黑樹條件的狀況下,即便一邊子樹全是黑色結點,另外一邊子樹全是紅黑相間,兩子樹的高度差也不會超過一半。一棵有 n 個結點的紅黑樹高度至多爲 2log(n+1),查找效率最壞爲 O(log(n))

因此紅黑樹常被用於需求查找效率穩定的場景,如 Linux 中內核使用它管理內存區域對象、Java8 中 HashMap 的實現等,因此瞭解紅黑樹也頗有意義。

下面介紹一下紅黑樹的等同 2-3-4樹。


2-3-4樹

定義

2-3-4樹是四階的 B樹(Balance Tree),它的結構有如下限制:

  • 全部葉子節點都擁有相同的深度。
  • 節點只能是 2-節點、3-節點、4-節點之一。

    • 2-節點:包含 1 個元素的節點,有 2 個子節點;
    • 3-節點:包含 2 個元素的節點,有 3 個子節點;
    • 4-節點:包含 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樹中結點添加須要遵照如下規則:

  • 插入都是向最下面一層插入;
  • 升元:將插入結點由 2-結點升級成 3-結點,或由 3-結點升級成 4-結點;
  • 向 4-結點插入元素後,須要將中間元素提到父結點升元,原結點變成兩個 2-結點,再把元素插入 2-結點中,若是父結點也是 4-結點,則遞歸向上層升元,至到根結點後將樹高加1;

而將這些規則對應到紅黑樹裏,就是:

  • 新插入的結點顏色爲紅色,這樣纔可能不會對紅黑樹的高度產生影響。
  • 2-結點對應紅黑樹中的單個黑色結點,插入時直接成功(對應 2-結點升元)。
  • 3-結點對應紅黑樹中的黑+紅子樹,插入後將其修復成 紅+黑+紅 子樹(對應 3-結點升元);
  • 4-結點對應紅黑樹中的紅+黑+紅子樹,插入後將其修復成紅色祖父+黑色父叔+紅色孩子子樹,而後再把祖父結點當成新插入的紅色結點遞歸向上層修復,直至修復成功或遇到 root 結點;

如上圖所示,雖然向紅黑樹中插入了一個新結點,但因爲旋轉和變色,子樹的高度保持不變。


刪除結點

紅黑樹的刪除要比插入要複雜一些,咱們仍是類比 2-3-4樹來說:

  • 查找最近的葉子結點中的元素替代被刪除元素,刪除替代元素後,從替代元素所處葉子結點開始處理;
  • 降元:4-結點變 3-結點,3-結點變 2-結點;
  • 2-結點中只有一個元素,因此借兄弟結點中的元素來補充刪除後的形成的空結點;
  • 當兄弟結點中也沒有多個元素能夠補充時,嘗試將父結點降元,失敗時向上遞歸,至到子樹降元成功或到 root 結點樹高減1;

將這些規則對應到紅黑樹中即:

  • 查找離當前結點最近的葉子結點做爲替代結點(左子樹的最右結點或右子樹的最左結點都能保證替換後保證二叉查找樹的結點的排序性質,葉子結點的替代結點是自身)替換掉被刪除結點,從替代的葉子結點向上遞歸修復;
  • 替代結點顏色爲紅色(對應 2-3-4樹中 4-結點或 3-結點)時刪除子結點直接成功;
  • 替代結點爲黑色(對應 2-3-4樹中 2-結點)時,意味着替代結點所在的子樹會降一層,須要依次檢驗如下三項,以恢復子樹高度:

    • 兄弟結點的子結點中有紅色結點(兄弟結點對應 3-結點或 4-結點)可以「借用」,旋轉過來後修正顏色;
    • 父結點是紅色結點(父結點對應 3-結點或 4-結點,能夠降元)時,將父結點變黑色,自身和兄弟結點變紅色後刪除;
    • 父結點和兄弟結點都是黑色時,將子樹降一層後把父結點看成替代結點遞歸向上處理。

如上圖,刪除的要點是 找到替代結點,若是替代結點是黑色,遞歸向上依次判斷侄子結點、父結點是否能夠補充被刪除的黑色,總體思想就是將刪除一個黑色結點形成的影響侷限在子樹內處理。


小結

固然實現過程當中調試也佔了很大一部分,我使用了兩項方法幫助調試:

  • 因爲插入多個結點時,沒法肯定是處理哪一個結點時出了問題,因而我給紅黑樹類添加了 debug 屬性,用二分法設置此屬性來找到問題結點;
  • 給紅黑樹類添加了 printTree() 方法,實時打印樹結構,肯定代碼問題再分析;

因爲紅黑樹相對其餘樹實在較爲複雜,只經過思考就徹底理解不太現實,還須要本身去試着畫,試着實現,我畫了 5 張 A4 紙的正反面纔算理解了紅黑樹,即使如此,在寫這篇文章時還發現了代碼中的可優化點。

並且代碼實現比畫圖還略複雜,理論中的一個旋轉就包含了 左旋/右旋/先左旋再右旋/先右旋再左旋 幾種狀況,雖然有必定規律,仍是本身實現一下印象最深入。

關於本文有什麼問題能夠在下面留言交流,若是您以爲本文對您有幫助,能夠點擊下面的 推薦 支持一下我,博客一直在更新,歡迎 關注 。

相關文章
相關標籤/搜索