一、紅黑樹介紹
1、定義
- 每個結點或者爲黑色或者爲紅色
- 根結點爲黑色
- 每個葉結點(實際上就是NULL指針)都是黑色的
- 如果一個結點是紅色的,那麼它的兩個子節點都是黑色的(也就是說,不能有兩個相鄰的紅色結點)
- 對於每個結點,從該結點到其所有子孫葉結點的路徑中所包含的黑色結點數量必須相同
2、二叉樹和AVL樹比較
1、二叉樹
- 任意節點左子樹不爲空,則左子樹的值均小於根節點的值.
- 任意節點右子樹不爲空,則右子樹的值均大於於根節點的值.
- 任意節點的左右子樹也分別是二叉查找樹
- 沒有鍵值相等的節點.
由二叉查找樹的性質可知,構成二叉樹的結構和插入數據的順序有關,存在不穩定性。最好情況可以構建成平衡二叉樹查找效率O(lgn),最壞情況也有可能構造成線性樹(查找效率O(n))
2、AVL樹
AVL樹是帶有平衡條件的二叉查找樹,一般是用平衡因子差值判斷是否平衡並通過旋轉來實現平衡。
左右子樹樹高不超過1,和紅黑樹相比,它是嚴格的平衡二叉樹,平衡條件必須滿足(所有節點的左右子樹高度差不超過1)。
AVL樹的插入 刪除元素必須保持絕對的平衡,所以從插入刪除元素起,要一直進行旋轉操作一直到根節點,所以
插入/刪除/查找操作比較穩定,時間複雜度是O(logn)
3、紅黑樹
紅黑樹也是一種平衡二叉樹,但是它與AVL樹相比是一種相對寬鬆的平衡,平衡因子是從任意非葉子節點到其所有葉子節點的路徑上黑色節點保持一致(紅黑樹性質5),所以當非葉子節點A到其一條路徑的葉子節點包含n個黑色節點,則A到其他路徑的所有節點最多隻能爲2n個,以此來保證易中動態平衡。
這種平衡與AVL樹相比能減少 增加和刪除節點時的時間消耗,因爲紅黑樹性質保證了新增刪除節點複雜度介於O(1)到O(logn)之間;
查找時,紅黑樹時間複雜度也爲O(logn),相比AVL樹的O(logn)只是多了一點係數,相對會慢一點。
但是綜合增加、刪除和查找性能看,紅黑樹還是優於AVL樹的。
二、紅黑樹實現
1、左旋、右旋
在紅黑樹中新加節點和刪除節點時,如果需要調整紅黑樹時就會涉及到節點的左旋和右旋。
左旋和右旋是一組對稱的操作
1.1、左旋
左旋靜態實現
動態實現
如上圖,E-S軸指向右下方,向左旋轉E-S軸時其指向右上方
1.2、右旋
靜態實現
動態實現
E-S軸向右旋轉
2、插入節點
2.1、插入過程
1)首先找插入位置。這個和二叉樹流程一樣,從根開始遍歷到葉子結點找到該插入的位置。
2) 插入值節點(紅色)
3) 確認插入值是否影響紅黑樹性質,影響就要做調整操作。
/*插入僞代碼*/
B-INSERT(T, z)
/*找待插入位置*/
y ← nil
x ← T.root
while x ≠ T.nil
do y ← x
if z.key < x.key
then x ← x.left
else x ← x.right
/*插入元素*/
z.p ← y
if y == nil[T]
then T.root ← z
else if z.key < y.key
then y.left ← z
else y.right ← z
z.left ← T.nil
z.right ← T.nil
z.color ← RED
/*調整紅黑樹*/
RB-INSERT-FIXUP(T, z)
插入節點的顏色選黑色,必定會破壞性質5(黑色節點增加),可能需要一層一層調整紅黑樹 ,而插入顏色選紅色,則有一半機率破壞性質4(父子節點都爲紅), 而且性質4調整比5調整更加簡單,所以插入顏色選擇紅色。
2.2、插入調整
如果待插入節點的父節點爲黑,則插入紅色不破壞紅黑樹性質,不需要調整,所以需要調整必然情況必然是父節點爲紅色。
RB-INSERT-FIXUP(T, z)
while z.p.color == RED
do if z.p == z.p.p.left
then y ← z.p.p.right
if y.color == RED
then z.p.color ← BLACK ▹ Case 2
y.color ← BLACK ▹ Case 2
z.p.p.color ← RED ▹ Case 2
z ← z.p.p ▹ Case 2
else if z == z.p.right
then z ← z.p ▹ Case 3
LEFT-ROTATE(T, z) ▹ Case 3
z.p.color ← BLACK ▹ Case 4
z.p.p.color ← RED ▹ Case 4
RIGHT-ROTATE(T, z.p.p) ▹ Case 4
else (same as then clause with "right" and "left" exchanged)
T.root.color ← BLACK
現分以插入節點的父節點是左子樹的幾種情況(右子樹同理):
1、如果樹是空樹,則插入的是根。
調整方案: 則直接顏色塗黑(終態)。
2、父節點是紅,叔叔節點是紅。
調整方案: 則父節點和叔叔節點都塗黑,祖父節點圖紅(將紅從父輩轉嫁到祖輩),將祖父節點設爲待插入節點繼續遞推(調整爲初始態)。
調整前:N爲插入元素
調整後:
3、父節點是紅,叔叔節點是黑,待插入節點是右子樹。
調整方案:將當前節點的父節點作爲新的當前節點,以新當前節點左旋(調整爲狀態4)。
調整前:
調整後:
4、父節點是紅,叔叔節點是黑,待插入節點是左子樹。
調整方案: 將祖父節點設爲紅,父節點設爲黑,以祖父節點右旋,相當於把父節點這邊的多餘紅色轉移到了叔叔節點那一側,修復了紅黑樹性質(終態)。
調整前:
調整後:
3、刪除節點
定義: 刪除節點是D, 刪除節點的父節點是P, 刪除節點的叔叔節點是U, 刪除節點的孩子節點是X
3.1、刪除過程
1、定位刪除節點並刪除
- 刪除節點D沒有子節點,直接刪除。
- 刪除的節點只有一個子節點,則刪除。
- 刪除的節點有兩個子節點,則找到這個結點的後繼結點(successor),也就是它的右子樹中最小的那個結點。然後我們將這兩個節點中的數據元素互換,將後繼節點作爲待刪除節點刪除。(後繼節點必然沒有左子樹,所以將情況轉換成2)
2、調整刪除節點的父節點P和子節點X的位置
- 如果刪除節點D沒有孩子節點,且他是根,則置樹爲空,否則D的父節點P指向D的指針置空。
- 如果刪除節點D有孩子節點X,且他是根,則將X置爲根節點,否則調整父節點P的孩子指針, 由原來指向D的指針指向X
3、調整紅黑樹
1 if left[z] = nil[T] or right[z] = nil[T]
2 then y ← z
3 else y ← TREE-SUCCESSOR(z)
4 if left[y] ≠ nil[T]
5 then x ← left[y]
6 else x ← right[y]
7 p[x] ← p[y]
8 if p[y] = nil[T]
9 then root[T] ← x
10 else if y = left[p[y]]
11 then left[p[y]] ← x
12 else right[p[y]] ← x
13 if y ≠ z
14 then key[z] ← key[y]
15 copy y's satellite data into z
16 if color[y] = BLACK
17 then RB-DELETE-FIXUP(T, x)
18 return y
3.2、紅黑樹調整
因爲刪除了一個D節點,如果D節點是紅色的話,不影響紅黑樹任何性質,所以可以不做調整,但是如果D是黑色,則D這一支子樹就會少一個黑節點,影響性質5,而且P的顏色和X的顏色如果都是紅色也會影響性質4,所以需要調整。所以調整時默認D是黑色,後面假設條件不做說明。
所以我們的思路是 可以將刪除的D節點的黑色標記附加在X上,把X節點叫做標記節點,顏色可以說成是紅+黑或者黑+黑,暫時保證性質5,然後我們在慢慢調整,將X上附加的顏色一層層往上推,最終調整紅黑樹保持平衡。
1 while x ≠ root[T] and color[x] = BLACK
2 do if x = left[p[x]]
3 then w ← right[p[x]]
4 if color[w] = RED
5 then color[w] ← BLACK ▹ Case 3
6 color[p[x]] ← RED ▹ Case 3
7 LEFT-ROTATE(T, p[x]) ▹ Case 3
8 w ← right[p[x]] ▹ Case 3
9 if color[left[w]] = BLACK and color[right[w]] = BLACK
10 then color[w] ← RED ▹ Case 2
11 x ← p[x] ▹ Case 2
12 else if color[right[w]] = BLACK and color[left[w]] = RED case 4
13 then color[left[w]] ← BLACK ▹ Case 4
14 color[w] ← RED ▹ Case 4
15 RIGHT-ROTATE(T, w) ▹ Case 4
16 w ← right[p[x]] ▹ Case 4
17 color[w] ← color[p[x]] ▹ Case 5
18 color[p[x]] ← BLACK ▹ Case 5
19 color[right[w]] ← BLACK ▹ Case 5
20 LEFT-ROTATE(T, p[x]) ▹ Case 5
21 x ← root[T] ▹ Case 5
22 else (same as then clause with "right" and "left" exchanged)
23 color[x] ← BLACK
1、如果標記節點X是紅+黑或者X是根節點。
調整: 直接將X設置成黑色,然後調整結束。
- 因爲X若是紅色,則可以把標記附加色設置進來,則D子樹的黑色節點數沒變,保證了性質5,同時也避免了X和P同時爲紅的情況保證了性質4。
- 因爲X若是根節點,則不管X原色是啥,都必須直接置黑,同時因爲已經遞推到了根,必然已經保證了性質5,所以附加的黑節點可以直接丟棄。
***後面幾種情況都是討論X是葉子節點的情形(D無子節點) 即 X是黑色,且X不是根結點。因爲D只有一個子節點或者沒有子節點,所以由性質5知 black_num(P->D->X) = black)num(P->D->nil),所以X必爲葉子節點 ***
2、如果標記節點X是黑+黑,且叔叔節點U是黑色,叔叔節點的孩子節點全是黑色。
調整: 將叔叔節點U置成紅色,同時X上的附加黑色上移到P節點,然後以P節點作爲當前節點,繼續執行調整操作。
因爲將U節點置成紅色,則保證了P的左右子樹黑色總數一致,然後以P爲當前節點繼續推進,但叔叔節點U置成紅色的前提是2個孩子節點都是黑色。
調整前:(其中的X左邊的黑色爲附加色)
調整後:
3、如果標記節點X是黑+黑,且叔叔節點U是紅色,叔叔節點U必有左右孩子且爲黑色。
調整:將父節點P設成紅色,叔叔節點U設爲黑色,然後以P爲節點左旋,之後U的左孩子作爲P的右孩子替代U,且U爲黑色。這樣調整主要是讓U節點滿足黑色條件,然後繼續進入調整流程。
調整前:
調整後:
4、如果標記節點X是黑+黑,且叔叔節點U是黑色,叔叔節點的左孩子是紅色,右孩子是黑色。
調整: 將叔叔節點U置成紅色,U的左孩子置成黑色,然後以U爲節點右旋,最終U的左孩子變成U節點且爲黑色,U的右孩子爲紅色。然後重新進入調整算法。
調整前:
調整後:
5、如果標記節點X是黑+黑,且叔叔節點U是黑色,右孩子是紅色色,左孩子是任意色。
調整: 將U的右子節點設置成黑色,叔叔節點U的顏色設置成父節點P的顏色,父節點P的顏色置黑(X上的附加黑色設置到P),然後以P爲節點左旋。這種調整實際上將U子樹多餘的紅節點移入到X子樹,保證了性質5,最終整個紅黑樹保持平衡,到達終態。
調整前:
調整後:
參考:
1、紅黑樹(red-black tree)算法
2、教你初步瞭解紅黑樹
3、左右旋圖參考