紅黑樹,顧名思義,就是把平衡二叉搜索樹的節點賦予兩種顏色,經過定義幾條規則,達到約束的目的。紅黑樹能夠保證,每次插入刪除操做後的重平衡,全樹拓撲結構的改變僅須要常數個節點,最壞狀況下須要對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來定義的,也就是說歷來沒有考慮被刪除節點原位置的紅黑關係,而是直接考慮交換後要刪除節點的父親、兄弟以及後繼的紅黑關係...這樣精簡了操做和代碼,只須要分爲四種狀況就能夠了,不過理解起來就變得複雜了。