從零開始的數據結構 —— 紅黑樹(6k字總結)

紅黑樹是一種常見的自平衡二叉查找樹,經常使用於關聯數組、字典,在各類語言的底層實現中被普遍應用,Java的TreeMap和TreeSet就是基於紅黑樹實現的。本篇分享將爲讀者講解紅黑樹的定義、建立和用途。javascript

一.紅黑樹的定義

(1)每一個節點或者是黑色,或者是紅色。
(2)根節點是黑色。
(3)每一個葉子節點是黑色。
(4)若是一個節點是紅色的,則它的子節點必須是黑色的
(5)從任意一個節點到葉子節點,通過的黑色節點是同樣的。

我直接抄了一段算法導論裏對於紅黑樹的描述,不少有關紅黑樹的講解和我同樣,上來就抄一段生硬的描述,讓人摸不着頭腦,本篇筆記但願可以由淺入深地、漸進式地引導讀者瞭解紅黑樹,所以咱們會先從紅黑樹的意義提及,爲何咱們須要一棵紅黑樹。html


二. 平衡二叉查找樹

咱們以這樣一個數組爲例 [42,37,18,12,11,6,5] 構建一棵二叉搜索樹,因爲數組中任意一點均可以做爲二叉搜索樹的根節點,所以這棵二叉搜索樹並不惟一,咱們來看一個極端的例子(以42做爲根節點,順序插入元素)前端


在這個例子中,二叉搜索樹退化成了鏈表,搜索的時間複雜度爲O(n),失去了做爲一棵二叉搜索樹的意義。爲了讓二叉搜索樹不至於太「傾斜」,咱們須要構建一棵平衡二叉搜索樹java


能夠看出,平衡二叉搜索樹的搜索時間複雜度爲O(logn),避免了由於隨機選取根節點構建二叉搜索樹而可能形成的退化成鏈表的狀況。下面再抄一段平衡二叉搜索樹的官方定義:node

平衡二叉查找樹:簡稱平衡二叉樹。是由前蘇聯的數學家 Adelse-Velskil和 Landis 在 1962 年提出的高度平衡的二叉樹,根據科學家的英文名也稱爲 AVL 樹。它具備以下幾個性質:
性質1. 能夠是空樹。
性質2 假如不是空樹,任何一個結點的左子樹與右子樹都是平衡二叉樹,而且高度之差的絕對值不超過 1

(若是讀者還不清楚平衡二叉搜索樹的概念,建議先查詢一下有關文檔,本篇筆記再也不詳細介紹平衡二叉搜索樹)算法


三. 2-3樹

通過上面的例子,咱們能夠知道,構建一棵平衡的二叉搜索樹的關鍵在於選取「正確」的根節點,那麼咱們如何在每次構建平衡二叉搜索樹時都能選取合適的根節點呢,這裏就要用到另外一種重要的樹:2-3樹(讀做二三樹),2-3樹和紅黑樹是等價的,理解2-3樹對理解紅黑樹以及B類樹都有很大的幫助。後端

2-3樹的基本概念

所謂2-3樹,即知足二叉搜索樹的性質,且節點能夠存放一個元素或者兩個元素,每一個節點有兩個或三個孩子的樹。數組

性質1. 知足二叉搜索樹的性質
性質2. 節點能夠存放一個或兩個元素
性質3. 每一個節點有兩個或三個子節點

2-3樹本質上也是一棵搜索樹,和二叉搜索樹的區別在於,2-3的節點可能存放2個元素,並且每一個節點可能擁有3個子節點。2-3樹存在如下兩種節點:2-節點(存在兩個子節點)和3-節點(存在3個子節點)數據結構

2-3樹的建立

下面咱們來看如何建立一棵2-3樹,建立2-3樹的規則以下:性能

規則1. 加入新節點時,不會往空的位置添加節點,而是添加到最後一個葉子節點上
規則2. 四節點能夠被分解三個2-節點組成的樹,而且分解後新樹的根節點須要向上和父節點融合

咱們依然使用上面的示例數組[42,37,18,12,11,6,5],依然使用順序插入的方式來構建2-3樹,看看是否會出現退化成鏈表的狀況。








咱們能夠注意到,在建立2-3樹的每一步中,整棵樹始終保持平衡。既然2-3樹已經可以保持自平衡,爲何咱們還須要一棵紅黑樹呢,這是由於2-3樹這種每一個節點儲存1~2個元素以及拆分節點向上融合的性質不便於代碼操做,所以咱們但願經過一些規則,將2-3樹轉換成二叉樹,且轉換後的二叉樹依然能保持平衡性。

2-3樹和紅黑樹的等價性

本小節咱們以一棵2-3樹爲例,將其從2-3樹轉換成爲一棵紅黑樹,從而學習瞭解2-3樹和紅黑樹的轉換規則,並體會2-3樹和紅黑樹之間的等價性。

對於2-3樹中的2-節點來講,自己就和二叉搜索樹的節點無異,能夠直接轉換爲紅黑樹的一個黑節點,可是對於3-節點來講,咱們須要進行一點小轉換:

1. 將3-節點拆開,成爲一棵樹,而且3-節點的左元素做爲右元素的子樹

2. 將原來的左元素標記爲紅色(表示紅色節點與其父節點在2-3樹中曾是平級的關係)


咱們來轉換一棵複雜點的2-3樹,根據上邊的兩條轉換規則,咱們將2-節點直接轉換爲黑色節點,將3-節點拆成一棵子樹,並給左元素標上紅色,這個過程應該不難理解,另外咱們能夠注意到,因爲紅色節點是由3-節點拆分而來,所以全部的紅色節點都只會出如今左子樹上。



四. 紅黑樹的性質和複雜度分析

紅黑樹基本性質分析

在完成了2-3樹到紅黑樹的轉換以後,咱們從新審視紅黑樹的五條性質:

(1) 每一個節點或者是黑色,或者是紅色

這是紅黑樹的定義,沒什麼好說的。

(2) 根節點是黑色

根節點要麼對應2-3樹的2-節點或者3-節點,而這二者的根節點都是黑色的,於是根節點必然是黑色。從上圖2-3樹節點和紅黑樹節點對應關係就能很容易看出來

(3) 每一個葉子節點是黑色

注意,這裏的葉子是指的爲空的葉子節點,上圖的紅黑樹的完整形式應該是這樣的:

(4) 若是一個節點是紅色的,則它的子節點必須是黑色的

因爲紅黑樹的每一個節點都由2-3樹轉換而來,紅色節點鏈接的節點必然是一個2-節點或者3-節點,而不管是2-節點仍是3-節點,其根節點都是黑色的,所以紅色節點的子節點必然是黑色的

(5) 從任意一個節點到葉子節點,通過的黑色節點是同樣多的

這是紅黑樹最重要的一條性質,也是紅黑樹的價值所在。因爲紅黑樹是由2-3樹轉換而來,所以每個黑色節點必然對應2-3樹的某個2-節點或者3-節點,所以紅黑樹的黑節點也能擁有2-3樹的平衡性。若是對這條性質還不夠理解,能夠對着上文2-3樹和紅黑樹的轉換圖再理解理解。


紅黑樹時間複雜度分析

網上有不少使用數學概括法來計算紅黑樹時間複雜度的證實了,這裏就再也不贅述。咱們能夠簡單思考一下,對於一棵普通的平衡二叉搜索樹來講,它的搜索時間複雜度爲O(logn),而做爲紅黑樹,存在着最壞的狀況,也就是查找的過程當中,通過的節點全都是原來2-3樹裏的3-節點,致使路徑延長兩倍,時間複雜度爲O(2logn),因爲時間複雜度的計算能夠忽略係數,所以紅黑樹的搜索時間複雜度依然是O(logn),固然,因爲這個係數的存在,在實際使用中,紅黑樹會比普通的平衡二叉樹(AVL樹)搜索效率要低一些。


既然紅黑樹的效率比AVL樹的效率低,那麼紅黑樹的優點體如今哪呢?事實上,紅黑樹的優點體如今它的插入和刪除操做上,紅黑樹的插入和刪除相較於AVL樹的性能會高一些,在後續紅黑樹的建立章節中,讀者應該可以體會到紅黑樹在調整樹平衡操做上的優點。


五. 紅黑樹的建立

上文中咱們講解了如何由2-3樹轉換一棵紅黑樹,下面咱們就來看看如何不通過2-3樹直接建立一棵紅黑樹,畢竟咱們寫代碼的時候不能先建立一棵2-3樹再轉化成紅黑樹吧。咱們回想一下2-3樹的建立規則:

規則1. 加入新節點時,不會往空的位置添加節點,而是添加到最後一個葉子節點上
規則2. 四節點能夠被分解三個2-節點組成的樹,而且分解後新樹的根節點須要向上和父節點融合

簡單來講,2-3樹的建立分爲「融合」和「拆分」兩步,爲了實現這兩步,咱們須要在建立二叉樹的基礎操做上增長另外幾個操做,分別是:

1. 保持根節點黑色

2. 左旋轉

3. 右旋轉

4. 顏色翻轉


保持根節點黑色和左旋轉

因爲咱們往2-3樹插入節點時作的都是融合,所以新加入的節點和原位置的節點是平級關係,因此咱們往紅黑樹裏增長節點的時候,增長的都是紅色節點。


咱們插入第一個紅色節點42,哦吼,第一步就與紅黑樹的性質2「根節點是黑色」衝突,爲了解決這種衝突,咱們將根節點變成黑色。


下面咱們繼續插入節點37,一樣的,新插入的節點都爲紅色


這裏咱們要思考一下,若是咱們顛倒順序,先插入37再插入42呢,是直接把42加到37的右子樹上麼,這顯然是錯誤的,由於在前邊2-3樹轉紅黑樹的過程當中,咱們已經瞭解到,全部的紅色節點都只會出如今左子樹上,所以咱們須要進行左旋轉,將節點的位置和顏色旋轉過來。




上面是兩個獨立的節點,若是節點擁有左右子樹,在旋轉後仍然能知足二叉搜索樹的性質嗎,咱們須要推廣到通常情形。咱們來看一個例子:









通過以上幾步,咱們就完成了通常情形下的左旋轉,咱們能夠對應地寫幾句僞代碼,這裏把37稱做node,42稱做target

function leftRotate(node) {​ 
​    node.right = target.left 
    target.left = node 
    target.color = node.color 
    node.color = RED
​​}複製代碼


顏色翻轉

上一小節咱們介紹了左旋轉的情形,其實左旋轉的情形就對應着2-3樹中生成3-節點的情形,也就是從2-節點到3-節點這一步,那麼從3-節點到4-節點,再到拆分4-節點的這一步又對應紅黑樹的什麼操做呢,咱們來看一個簡單的例子。

咱們以一棵已經擁有兩個節點的紅黑樹爲例,如今這一紅一黑兩個節點就對應了2-3樹的3-節點[37 42],咱們加入新的紅色節點66,節點66按照二叉搜索樹的原則,暫時加在節點42的右子樹上。以前咱們說過,紅色節點表示該節點與其父節點在2-3樹中是平級關係,也就是說這種左右子節點都是紅色節點的狀況其實對應了2-3樹中臨時的4-節點。固然,咱們知道紅色的節點是隻能出如今左子樹上,因此咱們須要進行一些變形。




咱們應該如何把這棵臨時的不規範的紅黑樹轉換成一棵正確的紅黑樹呢,回想2-3樹拆分4-節點的過程:4-節點被拆分爲3個2-節點,拆分後的子樹的根節點向上融合。因爲2-節點對應着紅黑樹中的黑色節點,所以咱們直接把左右子樹上的37和66直接翻轉爲黑色,此即顏色翻轉。因爲根節點還須要向上融合,所以咱們把根節點再標記爲紅色(至關於加入新節點)





咱們寫兩句代碼,畢竟這些翻來覆去的操做不是給人看的,是給機器看的。代碼很是簡單,就是把根節點變成紅色,左右子元素變成黑色。固然只有像上圖這種樹結構(根節點黑色而左右子元素紅色)才適用這種顏色翻轉。

function flipColors(node) {
    node.color = RED
    node.left.color = BLACK
    node.right.color = BLACK
}複製代碼


右旋轉

咱們剛纔插入了節點66,66比42大,所以被加入到了節點42的右子樹上,而後咱們使用顏色翻轉就完成了轉換。然而節點並不老是被添加到右子樹上,好比說插入節點12,12小於37,所以節點12被加入到37的左子樹上:


對於這種狀況,咱們須要進行右旋轉,咱們直接以通常狀況來說解:








通過以上幾步,咱們就完成了通常情形下的右旋轉,咱們能夠對應地寫幾句僞代碼,這裏把37稱做target,42稱做node。

function rightRotate(node) {  
    node.left = T1  
    target.right = node  
    target.color = node.color  
    node.color = RED
}複製代碼


其餘狀況

咱們經過顏色翻轉和右旋轉,解決了往3-節點添加節點的兩種狀況,分別是大於b節點狀況,小於a節點的狀況,那麼若是插入的節點大於a而小於b呢。



對於上圖的第三種狀況,咱們須要結合左旋轉、右旋轉、顏色翻轉等子過程來調整成爲一棵正確的紅黑樹,下面以[37 40 42]做爲例子:







流程總結

咱們總結一下以上三種情形,會發現其實紅黑樹插入節點不過五種形式

1. 插入到一個2-節點,且節點小於該2-節點


2. 插入到一個2-節點,且節點大於該2-節點



3. 插入到一個3-節點,且插入節點小於3-節點的兩個節點


4. 插入到一個3-節點,且插入節點大於3-節點的兩個節點


5. 插入到一個3-節點,且插入節點在3-節點的兩個節點之間


其實這五種形式均可以用一個邏輯鏈條來表示,咱們回顧一下6.4裏,插入的節點小於a大於b的轉換過程,出於通用性,我把具體數字隱去了。

咱們發現,這個流程已經包含了以上五種狀況,若是咱們插入的節點大於a也大於b,那麼咱們能夠直接跳到第四步,而後進行顏色翻轉;若是咱們插入的節點小於a也小於b,那麼跳到第三步;若是插入節點在ab之間,那麼就對應第二步。

三條線分別對應着3種3-節點插入節點的狀況

有了這個邏輯流程,咱們的代碼一會兒清晰起來,咱們只須要經過幾個條件判斷,就能描述紅黑樹全部旋轉方式,下面咱們來寫一段代碼:

function add(node) {  
    //若是右節點爲紅色,左節點爲黑色, 那麼進行左旋轉, 對應狀況2 
    if(isRed(node.right) && !isRed(node.left)) node = leftRotate(node)​  

    //若是左節點爲紅色,左節點的左節點也爲紅色, 那麼進行右旋轉, 對應狀況3 
    if(isRed(node.left) && isRed(node.left.left)) node = rightRotate(node)    

    //若是左右節點都爲紅色, 那麼進行顏色翻轉, 對應狀況4 
    if(isRed(node.left) && isRed(node.right)) flipColors(node)

}複製代碼


到這裏,咱們就實現了紅黑樹的全部平衡操做,從這個過程當中,咱們還能得出一個重要結論,即紅黑樹任何不平衡,都能在3次旋轉內獲得解決,這也就是紅黑樹相較AVL樹的優點所在。

​六. 紅黑樹和AVL樹的比較

1. AVL樹比紅黑樹更爲平衡,其搜索效率也好於紅黑樹, 通過咱們以前的分析能夠知道, 紅黑樹在最壞的狀況下搜索時間複雜度爲2logn,大於AVL樹的logn。AVL樹是嚴格平衡,紅黑樹只能達到「黑平衡」,即從任意節點出發到葉子節點通過的黑節點數量相同,但通過的紅色節點數量不肯定,最差的狀況下,通過的紅色節點和黑色節點同樣多。

2. 紅黑樹增刪節點的性能優於AVL樹,當咱們往紅黑樹增長節點或刪除節點引發紅黑樹不平衡,咱們只須要最多三次旋轉就能解決,而相同條件下,AVL樹的旋轉次數要多於紅黑樹,所以紅黑樹在增刪節點上相較於AVL樹更優


七.總結

最後作個歸納,紅黑樹是以犧牲部分搜索性能換取增刪性能的折中方案,用非嚴格的平衡,換取旋轉次數的減小。在實際使用中,若是所維護的樹須要頻繁增刪節點,紅黑樹會更加合適,反之,則適合AVL樹。


PS:其實全文最難的是排版,每個節點之間連線的曲直、每一張截圖邊框和內容的邊距,各張圖片的統一寬度、都是我顫顫巍巍一點一點用鼠標拖出來的(°ཀ°)

本文總結參考自BoBo老師的玩轉算法系列,BoBo老師的課程思路清晰,講解流暢,能在五千屢次評價後依然保持10分的滿分,實屬可貴!不管你是前端後端,甚至不管你是否從事計算機行業,我我的都以爲,這個課程值得一看

紅黑樹(R-B Tree)

www.cnblogs.com/xuxinstyle/…

B樹,B-樹,B*樹,B+和紅黑樹的區別:

www.cnblogs.com/myseries/p/…

數據結構之讀懂紅黑樹:

www.jianshu.com/p/05b45541b…

相關文章
相關標籤/搜索