前言
上一篇博客介紹了[二叉樹].二叉搜索樹在樹是平衡的情況下搜索、插入和刪除的效率都很好,但是如果二叉搜索樹是不平衡的那么它的效率就不那么令人滿意了,而紅黑樹解決了二叉搜索樹的這個問題,可以始終保持樹是平衡(大致平衡)的.
閱讀前須知:
- 如果您對二叉樹不太了解,請移步[二叉樹]
- 本文用到的評估紅黑樹效率的方法-- 大O表示法
- 由於紅黑樹的實現代碼過於晦澀難懂,所以本篇博客只會通過通俗易懂的語言加上一目了然的圖片對紅黑樹進行講解
- 本文的側重點是紅黑樹的插入過程
- 假定插入紅黑樹的值不會重復
1. 紅黑樹簡介
紅黑樹是什么? 其實紅黑樹就是加了一些特殊規則(保持樹平衡的規則)的二叉搜索樹.
紅黑樹的兩個特征:
- 節點都有顏色
- 在刪除和插入過程中,保證這些顏色不同排列的規則(紅黑規則)
當插入(或刪除)一個節點時,必須遵守紅黑規則以保持樹是大體平衡的,紅黑規則:
- 每個節點不是紅色的就是黑色的
- 根節點是黑色的
- 所有葉子都是黑色(葉子是NIL節點或NULL). (NIL: NIL表示無值,任何變量在沒有被賦值之前的值都為NIL)
- 紅色節點的子節點必須是黑色的(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點),反之不成立
- 從根到葉節點或空子節點的每條路徑,必須包含相同數目的黑色節點(也可稱為黑色高度).
紅黑規則的圖解:
圖1
FBI WARNING: 本文接下來給出的圖例中將會省略規則3中的NIL葉節點, 特此強調
關於規則5空子節點的示例,可以看到下圖中根到葉節點和根到空子節點的高度不一致,違背了規則5:
圖2
下面是紅黑規則的總結,重要結論->重要結論->重要結論,紅黑規則確保了紅黑樹的關鍵特性:從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長。結果是這個樹大致上是平衡的。因為操作比如插入、刪除和查找某個值的最壞情況時間都要求與樹的高度成比例,這個在高度上的理論上限允許紅黑樹在最壞情況下都是高效的(如果插入的數據是有序的,紅黑樹不會出現極端不平衡的情況,會自己修復),而不同於普通的二叉搜索樹。
是規則4導致路徑上不能有兩個連續的紅色節點確保了這個結果。最短的可能路徑都是黑色節點,最長的可能路徑有交替的紅色和黑色節點。因為根據規則5所有路徑都有相同數目的黑色節點,這就表明了沒有路徑能多於任何其他路徑的兩倍長。
2. 了解插入過程前的一些熱身准備
在了解紅黑樹插入一個新節點的時候都做了什么以前,有一些插入過程中用到的概念會在本章節進行講解,如果你對變色、旋轉有所了解,可以跳過本節.
首先紅黑樹新插入的節點都是紅色的,並且紅黑樹是在插入數據的過程中對樹進行平衡修復的,如果感知到違反了紅黑規則,程序會進行自我修復以符合紅黑規則,修復方法有且只有兩種:
- 改變節點的顏色
- 執行旋轉操作
接下來會對一下三個方面做出解釋:
- 為什么新插入的節點是紅色的?
- 改變節點的顏色?
- 旋轉操作?
2.1 新插入的節點都是紅色的
為什么新插入的節點都是紅色的?不是黑色的?
主要有兩方面原因:
- 不會違背規則5,從根到所有葉或空子節點的黑色高度不會改變
- 雖然有可能會違背規則4出現兩個連續的紅色節點,但是只有50%的幾率碰上,並且就算碰上了,修復規則4也比修復規則5要簡單的多(只需要幾次變色和幾次旋轉就能修復規則4).
2.2 變色
變色..嗯就是變色,紅的變成黑的,或者黑的變成紅的. 至於什么情況下需要變色,怎么變色,需要具體問題具體分析,看完章節3就曉得了.
2.3 旋轉
樹左右兩邊的節點數量不相同的時候樹就是不平衡的了,如果這時候想把樹變為平衡的,就需要旋轉.
旋轉必須一次做兩件事:
- 使一些節點上升,一些節點下降,幫助樹平衡
- 保證不破壞二叉搜索樹的特征.
二叉搜索樹的特征: 任何節點的左子節點及其子樹的關鍵字都小於該節點,而它的右子節點及其子樹的關鍵字都大於等於它.
關於旋轉這個動作的定義: 選擇一個節點作為旋轉的"頂端"(top),如果做一次右旋,這個"頂端節點"將會向下和向右移動到它的右子節點的位置,它的左子節點將會上移到它原來的位置.
以關鍵字值為10的節點為頂端,執行左旋轉和右旋轉:
圖3
注意事項: 左旋轉必須有右子節點,右旋轉必須有左子節點.
關於旋轉有一個情況要注意: 如果旋轉的頂端節點有內側子孫節點,那么執行完旋轉操作以后,這個內側子孫節點要斷開與父節點的連接,連接到頂端節點上.
圖4
如果內側子孫節點是一顆子樹,那么整棵樹也一起移動過去.
3. 插入數據的過程
准備活動做完了,下面開始正題.
在紅黑樹中插入一個新節點的步驟和二叉搜索樹有部分是相同的:從根開始,插入節點的關鍵字與當前節點的關鍵字進行比較,如果小往左走,如果大於等於該節點往右走,直到找到一個合適的位置進行插入.
不同點:
- 在向下比較的過程中需要變色
- 在向下比較的過程中需要旋轉
- 插入新節點之后需要旋轉
下面來概述一下紅黑樹的插入過程: 從根開始比較關鍵字的值,如果小往左走,如果大於等於該節點往右走(這是相同點),從根往下走的過程中一旦發現有黑色節點有兩個紅色子節點的情況進行變色,變成紅色節點有兩個黑色節點(這是不同點1),變完色有可能會違背規則4不能出現連續的兩個紅色節點,這時候需要再次進行旋轉和變色(這是不同點2),最后找到合適的位置插入新節點,如果新插入節點的父節點是黑色就結束了,如果是紅色又違背了規則4,那么還需要旋轉和變色(這是不同點3).
現在不懂沒關系, 3.1、3.2、3.3 三個章節會通過一個完整的示例,詳細的講述紅黑樹的插入過程.
圖5
我們以往這棵樹插入值為19的新節點為例
3.1 在向下比較的過程中需要變色
圖6
變色原則:
- 在向下比較的過程中如果遇到一個黑色節點有兩個紅色節點,這時候要把它的顏色對調變成一個紅色節點兩個黑色節點.
- 如果這個節點是根節點,那么就都變成黑色節點.
圖7
變色后:
圖8
至於為什么要變色: 只要應用了變色原則,插入節點后的旋轉不會造成樹的上方違背紅黑原則.
3.2 在向下比較的過程中需要旋轉
變色后出現了連續的紅色節點,這時候我們需要進行旋轉和變色修復紅黑樹,我們聲明X、P、G三個節點:
- X是P的子節點
- P是G的子節點
- X是G的子孫節點
圖9
X、P是連續紅色節點的情況違背了規則4,這時候就要看X與G的位置關系了,X與G位置的不同,修復的步驟也不同:
- X是G外側紅色子孫節點的情況,需要旋轉一次,變色兩次.
- X是G內側紅色子孫節點的情況,需要旋轉兩次,變色兩次.
關於外側子孫節點和內側子孫節點位置的說明:
- 如果P是G的左子節點,如果X是P的左子節點,那么X是G的外側子孫節點
- 如果P是G的左子節點,如果X是P的右子節點,那么X是G的內側子孫節點
(如果P是G的右子節點,X是P的右子節點,那么X是G的外側子孫節點)
回歸正題,X是G外側紅色子孫節點的情況:
圖10
X是G內側紅色子孫節點的情況:
圖11
3.3 插入新節點之后需要旋轉
終於到最后一步了,新節點插入后(X),要根據父節點(P)的顏色,和它與祖父節點的位置進行調整,共有三種情況:
- P是黑色,直接結束
- P是紅色,X是G的外側子孫節點
- P是紅色,X是G的內側子孫節點
P是紅色,X是G的外側子孫節點:
圖12
P是紅色,X是G的內側子孫節點:
圖13
以上就是紅黑樹插入一個新節點的完成過程.
4. 紅黑樹的效率
紅黑樹的效率與二叉搜索樹相同都為為O(logN),紅黑樹的查找速度和二叉搜索樹幾乎一樣,因為查找過程並沒有應用紅黑樹的特征.只是額外增加了一些存儲紅-黑顏色的(boolean變量)存儲空間.
至於插入和刪除因為要加入一些變色和旋轉,所以要比二叉搜樹慢一些,但是效率同樣也為O(logN).
5. 紅黑樹的總結,優缺點
優點:
- 查找速度和二叉搜索樹相同
- 如果插入的是有序數據效率不會慢到O(N),這點爆了二叉搜索樹
缺點:
- 增、刪略慢於二叉搜索樹
- 代碼實現過於復雜...
6. 實際中的應用
Java中的TreeSet、TreeMap、Java8中的hashmap、Linux內核,面試官的靈魂拷問等等很多地方都用到了紅黑樹.
7. 最早的平衡樹AVL樹
AVL樹是最早的平衡樹,它是由AV(Adelson-Velskii)和L(Landis)兩個發明者名字的縮寫命名的.
AVL樹自平衡的方式: 在節點中保存左右子節點的高度,如果高度差大於1就進行自平衡(旋轉).
插入過程:
- 插入之后,檢查新節點插入點所在的最低子樹的根,如果它的子節點的高度相差大於1,執行一次或者兩次旋轉使他們的高度相等.
- 然后算法向上移動,檢查上面的節點,必要時均衡高度.這個檢測檢查所有路徑一直向上,直到根為止.
AVL樹的效率:
- 查找的時間復雜度為O(logN)
- 插入(或刪除)由於需要掃描兩趟,一次向下插入,一次向上平衡比,所以效率不如紅黑樹高.
結論: AVL樹因為插入(或刪除)需要掃描兩趟所以效率不如紅黑樹高,也不如紅黑樹常用,了解即可
8. 下期預告
紅黑樹的知識總結就到這里了,下一篇博客會更新2-3-4樹、B-樹(不是B減樹是橫杠)、B+樹的知識,還有紅黑樹、二叉樹與這些樹的對比等內容,敬請期待.