紅黑樹首先是一棵二叉查找樹(BST),BST 滿足的性質如下:
- 左子樹上所有節點的值均小于或等于它的根節點的值;
- 右子樹上所有節點的值均大于或等于它的根節點的值;
- 左右子樹? BST。
考慮向一棵 BST 中多次插入新節點的情況,如果插入的總是最大(小)值,會導致 BST 嚴重不平衡。為了解決這個問題,引入了自平衡的二叉查找樹——紅黑樹(BR-Tree)。紅黑樹的附加特性:
- 節點為黑色或紅色;
- 根節點為黑色;
- 每個葉子節點都是黑色的空節點(NIL);
- 每個紅色節點的兩個孩子節點都是黑色(保證從每個葉子到根的所有路徑上不能有兩個連續的紅色節點);
- 從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點(保證了紅黑樹從根到葉子的最長路徑不會超過最短路徑的兩倍)。
紅黑樹的用途
1)廣泛應用在 C++ 的 STL 中,如 map 和 set 都是用紅黑樹實現的。
2)linux 進程調度,用紅黑樹管理進程控制塊
3)epoll 在內核中的實現,用紅黑樹管理事件塊
4)nginx 中,用紅黑樹管理 timer 等
紅黑樹的插入
插入的新節點 N 初始化為紅色。
case 0.0:
N 為根節點,直接變為黑色。over。
case 0.1:
N 的父節點為黑色,無需調整。over。
case 1:
N 的父節點為紅色,無叔叔節點(父節點的兄弟節點)或叔叔節點為黑色,且 N 為右子。以父節點為新紅色節點 N,并以 N 為支點進行左旋。繼續判定旋轉后的節點 N 是否滿足性質。
case 2:
N 的父節點為紅色,無叔叔節點(父節點的兄弟節點)或叔叔節點為黑色,且 N 為左子。將 N 的父節點變為黑色,祖父節點變為紅色,以祖父節點為支點進行右旋。
case 3:
N 的父節點為紅色,叔叔節點(父節點的兄弟節點)也為紅色,將父節點與叔叔節點都變為黑色,祖父節點變為紅色。若祖父節點仍不滿足性質,則將祖父節點當做新紅色節點遞歸調整。最后強制根節點為黑色。
紅黑樹的刪除
case 1:
如果需要刪除的節點有兩個孩子,我們的做法是找到這個節點的中序后繼,將后繼節點中的數據拷貝至待刪除結點,然后刪除后繼節點。而后繼節點必然最多只有一個子節點,這樣我們就把刪除兩個孩子的節點轉為刪除一個孩子的節點(case 2)。
case 2:
刪除只有一個孩子的節點,如果它兩個孩子都為空,即均為葉子,我們任意將其中一個看作它的孩子。(這里體現出來,在紅黑樹里特別指定葉子結點為 NIL 節點的作用,NIL 節點經常可以充當正常節點使用以使得算法的表達更加容易。)
case 2.1:
若被刪除節點是紅色,它的父親和孩子一定是黑色的。所以我們可以簡單地用它的黑色孩子替換它。
case 2.2:
若被刪除節點是黑色而它的孩子是紅色。用它的紅色孩子頂替上來并重繪為黑色。
case 2.3
若待刪除節點和它的子節點都是黑色,情況比較多,由簡單到復雜分為六小類。
先來約定涉及到的節點的名稱。我們先用待刪除節點的孩子代替待刪除節點,并且記這個孩子為 N,記它的新的父節點為 P,它的兄弟節點為 S, S的左孩子為 SL,右孩子為 SR。
case 2.3.1:
若 N 是新的根。無需改動,所有屬性都保持著。over。
case 2.3.2:
P 為紅色,S 和 S 的兩個孩子都是黑色的。將 P 置為黑色,S 置為紅色。這樣,不經過 N 的路徑上的黑色結點數目并沒有發生變化,而經過 N 結點的路徑上黑色結點的數目增加了 1,剛好添補了這條路徑上刪除的黑色結點。所以紅黑樹又重新達到了平衡。
case 2.3.3:
S 是黑色,S 的右孩子是紅色,而 N 是它父親的左兒子。以 P 為支點左旋,這樣 S 成為 P 和 R 的父親。接著交換 N 的父親和 S 的顏色,并使 S 的右兒子為黑色。
case 2.3.4:
S 是黑色,S 的左孩子是紅色,S 的右兒子是黑色,而 N 是它父親的左兒子。在這種情況下我們在 S 上做右旋,這樣 S 的左兒子成為 N 的新兄弟。我們接著交換 S 和它的新父親的顏色。所有路徑仍有同樣數目的黑色節點,但是現在N有了一個右兒子是紅色的黑色兄弟,所以我們進入了 case 2.3.3。N 和它的父親都不受這個變換的影響。
case 2.3.5:
S 是紅色。在這種情況下我們在 P 上做左旋,把紅色兄弟轉換成 N 的祖父。我們接著對調 N 的父親和祖父的顏色。盡管所有的路徑仍然有相同數目的黑色節點,現在 N 有了一個黑色的兄弟和一個紅色的父親,所以我們可以接下去按 case 2.3.2、case 2.3.3、case 2.3.3 來處理。
case 2.3.6:
P、S 和 S 的兒子都是黑色的。在這種情況下,我們簡單的重繪 S 為紅色。結果是通過 S 的所有路徑,都少了一個黑色節點。 因為刪除 N 的初始的父親使通過 N 的所有路徑少了一個黑色節點,這使事情都平衡了起來。 但是,通過 P 的所有路徑現在比不通過 P 的路徑少了一個黑色節點。要修正這個問題,我們要轉到 case 2.3.1,把 P 當做新的 N 重新平衡處理。