紅黑樹(Red-Black Tree)

紅黑樹,顧名思義,就是把平衡二叉搜索樹的節點賦予兩種顏色,經過定義幾條規則,達到約束的目的。紅黑樹能夠保證,每次插入刪除操做後的重平衡,全樹拓撲結構的改變僅須要常數個節點,最壞狀況下須要對logn個節點重染色,可是就分攤意義仍然爲O(1)。函數

須要知足的條件:spa

(1)樹根始終爲黑色3d

(2)外部節點均爲黑色code

(3)紅節點的孩子必然爲黑色blog

(4)任一外部節點到根節點的路徑,黑節點數量相同繼承

從紅黑樹的規則,也能夠獲得一些結論,好比:紅節點的父親必然是黑節點;任一路徑黑節點必然很多於紅節點。遞歸

根據第(4)條規則,能夠定義,從根節點通往任一節點的,除去根節點自己,沿途所通過黑節點的總數稱爲節點的黑深度(black depth)。因此也能夠理解爲,全部外部節點的黑深度一致。與樹高相似,從任一節點通往其任一後代的沿途,通過的黑節點總數稱爲該節點的黑高度(black height)。rem

從黑高度的定義能夠天然想到,紅節點依賴於黑節點而存在,它們的存在甚至都不影響深度,是一種伴隨的狀態。事實上,紅黑樹能夠認爲是一種特殊的B-樹,即(2,4)-樹。由於紅節點的父親必然爲黑節點,故能夠把黑節點和他的紅節點孩子看做一個B-樹節點的關鍵碼,這樣就與(2,4)-樹等效,其中黑節點必然位於中間,紅關鍵碼不超過兩個。class

根據紅黑樹的定義,紅黑樹的高度範圍爲[log2(n+1),2*log2(n+1)],任一節點的左右高度(不是黑高度)相差不超過兩倍。date

紅黑樹的實現

紅黑樹能夠繼承二叉搜索樹,只須要重寫插入和刪除操做便可。同時,還須要從新定義高度,此時高度僅指黑高度。通常地外部節點(即便不存在)都認爲是黑節點。

插入

根據紅黑樹的4個要求,能夠天然想到,插入的節點不可能爲黑,不然將完全打破全樹的平衡,若是父親以及兄弟節點均爲黑,那麼將難以調整。反之,插入的節點統一爲紅色,這樣僅會致使局部不知足規則(3)。所以,插入後產生這種問題,稱爲「雙紅」。

考慮插入後的幾種狀況:

(1)父親節點爲黑,這樣不須要調整。

(2)父親節點爲紅,父親的兄弟節點爲黑。這時即構成了一個3-4問題,對這幾個節點進行旋轉操做,並將父親節點染色爲黑,祖父染爲紅便可。這種狀況只須要1-2次旋轉,2次染色,調整便可完成。

(3)父親節點爲紅,父親的兄弟節點也爲紅。此時沒法經過旋轉操做,考慮相似B-樹的上溢操做,即節點由於超過4度而上溢。從B-樹的觀點看,將祖父上溢一層,兩側的節點分裂爲兩個新節點,並將父親節點染黑,父親的兄弟節點也染黑便可。祖父上溢後,可能會致使上一層的雙紅,因而須要繼續向上遞歸。若是遞歸至根節點,那麼須要把g染黑,同時全樹的高度+1。這種狀況每次操做只須要三次染色,但可能最多須要O(logn)次迭代。

刪除

紅黑樹的刪除操做,與通常二叉搜索樹相同,先找到要刪除的元素,而後再刪除便可。先進行查找以及替換工做,若是被刪除的元素兩個孩子不都存在,替換爲存在的孩子;若是兩個孩子都存在,與直接後繼交換。x爲實際被刪除的節點,而r是它的接替者,r的兄弟爲外部節點w=NULL(由於x是後繼,x不可能有左孩子),這樣能夠將x統一視爲雙分支的節點,方便統一處理。(要注意,這裏已經進行了替換,即x是實際刪除的節點,後繼須要按照二叉樹的後繼來尋找,並交換他們的數值。若是要刪除的是葉節點,那麼不存在後繼,w原本即爲NULL,不須要假設一個,直接釋放掉這個節點。總之,實際處理的節點是原來節點的後繼,不過已經交換了數值,r是該節點的後繼,即原來節點位置後繼的後繼)

替換以及刪除的代碼以下:

 1 template<typename T> static BinNodePosi(T) removeAt(BinNodePosi(T)& x, BinNodePosi(T)& hot)
 2 {
 3     BinNodePosi(T) w = x;//實際被摘除的節點
 4     BinNodePosi(T) succ = NULL;
 5     if (!(x->lc))//若是左孩子爲空
 6         succ = x = x->rc;//直接替換爲右子樹
 7     else if (!(x->rc))//若是右孩子爲空
 8         succ = x = x->lc;
 9     else//左右孩子都存在,選擇x的直接後繼做爲實際摘除的節點(能夠畫個圖來理解)
10     {
11         w = w->succ();
12         swap(x->data, w->data);//交換x與其直接後繼的數值
13         BinNodePosi(T) u = w->parent;
14         ((u == x) ? u->rc : u->lc) = succ = w->rc;//隔離節點w,把w的孩子與原樹鏈接起來
15     }
16     hot = w->parent;//記錄被刪除節點的父親
17     if (succ) succ->parent = hot;//將被刪除節點的接替者與hot鏈接
18     release(w->data); release(w); //釋放被刪除的節點
19     return succ;//返回後繼(接替)
20 }
21 template<typename T> bool RedBlack<T>::remove(const T& e)
22 {
23     BinNodePosi(T)& x = search(e); if (!x) return false;
24     BinNodePosi(T) r = removeAt(x, _hot); if (!(--_size)) return true;//刪除後不存在元素直接返回
25     if (!_hot)//刪除的節點爲根節點
26     {
27         _root->color = RB_BLACK; updateHeight(_root); return true;
28     }
29     if (BalckHegihtUpdated(*_hot)) return true;//若是祖先黑深度依然平衡,便可返回
30     if (IsRed(r))//若是接替的節點爲紅,直接轉爲黑便可
31     {
32         r->color = RB_BLACK; r->height++; return true;
33     }
34     //不然,須要進行雙黑調整
35     solveDoubleBlack(r); return true;
36 }

 

這樣,若是實際被刪除的節點x爲紅色,r爲黑色,那麼直接刪除x,將r接入便可,能夠等效視做拋棄w。若是x爲黑色,r爲紅色,那麼刪除x後將r染黑接入便可。

不過刪除的時候,可能形成雙黑問題,即被刪除的節點和接替的後繼,都是黑色,這樣會致使局部高度下降,從而不知足紅黑樹的條件。假設實際被刪除節點爲x,接替者爲r,兄弟爲s,兄弟的一個孩子爲t,x的父親爲p,在這裏把雙黑分爲四種狀況:
(1)s有一個孩子t爲紅色,另一個孩子不肯定顏色,p的顏色也不肯定。此時,能夠考慮旋轉操做(在B-樹中爲下溢操做),即s爲軸的zig旋轉,而且把p和t染黑,s繼承p的顏色。這樣,刪除操做即宣告完成。

(2)s及其兩個孩子均爲黑色,p爲紅色。此時,刪除x後,能夠將p「拉下來」,合併爲一個新節點,而且交換p和s的顏色。由於從B-樹的意義,p爲紅色,那麼p的兄弟必然有一個爲黑色,故不可能發生新的下溢。

(3)s及其兩個孩子均爲黑色,p爲黑色。這種狀況,刪除x後,一樣將p降低一層,並把s染爲紅色。此時,子樹總體黑高度降低,必然引起持續上層下溢,須要繼續迭代。

(4)s爲紅色,p必爲黑色。此時,刪除x僅形成以x爲根的子樹高度降低,故從紅黑樹角度,以p爲軸進行一次旋轉,並交換s和p的顏色便可。此時會有新的問題,即r的高度並無恢復。可是,r有了新的兄弟s',而s'必然是黑節點,此時問題轉換爲了(1)或者(2):若是s'有紅孩子,那麼轉換爲(1),若是s'沒有紅孩子,轉換爲(2)。所以,這種狀況也不須要迭代,只須要4+1或者4+2。

實現代碼徹底照抄的0 0之後有時間必定本身寫一下

 1 template<typename T> void RedBlack<T>::solveDoubleBlack(BinNodePosi(T) r)//輸入爲用來替換x的後繼
 2 {
 3     BinNodePosi(T) p = r ? r->parent : _hot; if (!p) return;//r的父親,不存在能夠直接退出
 4     BinNodePosi(T) s = (p->lc == r) ? p->rc : p->lc;//s爲r的兄弟
 5     if (IsBlack(s))//s爲黑
 6     {
 7         BinNodePosi(T) t = NULL;//s的紅孩子(若左右均紅,左優先)
 8         if (IsRed(s->rc)) t = s->rc;
 9         if (IsRed(s->lc)) t = s->lc;
10         if (t)//s爲黑且有紅孩子的時候(BB-1)
11         {
12             RBColor oldColor = p->color;//備份原子樹根節點p的顏色
13             //進行旋轉重平衡並將新子樹的左右染黑
14             BinNodePosi(T) b = FromParentTo(*p) = rotateAt(t);//返回子樹父親節點
15             if (HasLChild(*b)) { b->lc->color = RB_BLACK; updateHeight(b->lc); }
16             if (HasRChild(*b)) { b->rc->color = RB_BLACK; updateHeight(b->rc); }
17             b->color = oldColor; updateHeight(b);
18         }
19         else//s爲黑可是沒有紅孩子
20         {
21             s->color = RB_RED; s->height--;//s轉紅
22             if (IsRed(p))//BB-2R
23                 p->color = RB_BLACK;//p轉黑可是高度不變
24             else//BB-2B
25             {
26                 p->height--;//p保持黑可是高度降低
27                 solveDoubleBlack(p);//遞歸上溯
28             }
29         }
30     }
31     else//s爲紅,p以及s的兩個孩子必然都黑
32     {
33         s->color = RB_BLACK; p->color = RB_RED;//s轉黑p轉紅,交換
34         BinNodePosi(T) t = IsLChild(*s) ? s->lc; s->rc;//取t與父親s同側
35         _hot = p; FromParentTo(*p) = rotateAt(t);//p原父親的孩子爲如今的根節點
36         solveDoubleBlack(r);//p爲紅,後續只能爲BB-1或BB-2R
37     }
38 }

有問題能夠參考鄧俊輝大大的原書,雖然我以爲我解釋地比原書好一些了,我看原書看了半天才看明白...主要是在二叉搜索樹中的removeAt()函數,這個函數會執行一個比較聰明的對策:不真的刪除節點後再鏈接節點,而是交換內部數值後,刪除那個交換後的節點,這個實際被刪除的節點是原節點的後繼,再把子樹接入被刪除節點的父親位置_hot。而在這裏,x就是這個後繼,而r是x的後繼,能夠說是後繼的後繼。一切p s r t都是從x來定義的,也就是說歷來沒有考慮被刪除節點原位置的紅黑關係,而是直接考慮交換後要刪除節點的父親、兄弟以及後繼的紅黑關係...這樣精簡了操做和代碼,只須要分爲四種狀況就能夠了,不過理解起來就變得複雜了。

相關文章
相關標籤/搜索