轉c++
推薦閱讀:Left-Leaning Red-Black Trees, Dagstuhl Workshop on Data Structures, Wadern, Germany, February, 2008. 算法
直接下載:http://www.cs.princeton.edu/~rs/talks/LLRB/RedBlack.pdfspa
------------------------------
紅黑樹系列,六篇文章於今日已經完成:.net
------------------------------指針
1、紅黑樹的介紹code
先來看下算法導論對R-B Tree的介紹:
紅黑樹,一種二叉查找樹,但在每一個結點上增長一個存儲位表示結點的顏色,能夠是Red或Black。
經過對任何一條從根到葉子的路徑上各個結點着色方式的限制,紅黑樹確保沒有一條路徑會比其
他路徑長出倆倍,於是是接近平衡的。blog
前面說了,紅黑樹,是一種二叉查找樹,既然是二叉查找樹,那麼它必知足二叉查找樹的通常性質。
下面,在具體介紹紅黑樹以前,我們先來了解下 二叉查找樹的通常性質:
1.在一棵二叉查找樹上,執行查找、插入、刪除等操做,的時間複雜度爲O(lgn)。
由於,一棵由n個結點,隨機構造的二叉查找樹的高度爲lgn,因此瓜熟蒂落,通常操做的執行時間爲O(lgn)。
//至於n個結點的二叉樹高度爲lgn的證實,可參考算法導論 第12章 二叉查找樹 第12.4節。
2.但如果一棵具備n個結點的線性鏈,則此些操做最壞狀況運行時間爲O(n)。圖片
而紅黑樹,能保證在最壞狀況下,基本的動態幾何操做的時間均爲O(lgn)。unicode
ok,咱們知道,紅黑樹上每一個結點內含五個域,color,key,left,right,p。若是相應的指針域沒有,則設爲NIL。get
通常的,紅黑樹,知足如下性質,即只有知足如下所有性質的樹,咱們才稱之爲紅黑樹:
1)每一個結點要麼是紅的,要麼是黑的。
2)根結點是黑的。
3)每一個葉結點,即空結點(NIL)是黑的。
4)若是一個結點是紅的,那麼它的倆個兒子都是黑的。
5)對每一個結點,從該結點到其子孫結點的全部路徑上包含相同數目的黑結點。
下圖所示,便是一顆紅黑樹:
此圖忽略了葉子和根部的父結點。
2、樹的旋轉知識
當咱們在對紅黑樹進行插入和刪除等操做時,對樹作了修改,那麼可能會違背紅黑樹的性質。
爲了保持紅黑樹的性質,咱們能夠經過對樹進行旋轉,即修改樹種某些結點的顏色及指針結構,以達到對紅黑樹進行
插入、刪除結點等操做時,紅黑樹依然能保持它特有的性質(如上文所述的,五點性質)。
樹的旋轉,分爲左旋和右旋,如下藉助圖來作形象的解釋和介紹:
1.左旋
如上圖所示:
當在某個結點pivot上,作左旋操做時,咱們假設它的右孩子y不是NIL[T],pivot能夠爲樹內任意右孩子而不是NIL[T]的結點。
左旋以pivot到y之間的鏈爲「支軸」進行,它使y成爲該孩子樹新的根,而y的左孩子b則成爲pivot的右孩子。
來看算法導論對此操做的算法實現(以x代替上述的pivot):
LEFT-ROTATE(T, x)
1 y ← right[x] ▹ Set y.
2 right[x] ← left[y] ▹ Turn y's left subtree into x's right subtree.
3 p[left[y]] ← x
4 p[y] ← p[x] ▹ Link x's parent to y.
5 if p[x] = nil[T]
6 then root[T] ← y
7 else if x = left[p[x]]
8 then left[p[x]] ← y
9 else right[p[x]] ← y
10 left[y] ← x ▹ Put x on y's left.
11 p[x] ← y
2.右旋
右旋與左旋差很少,再此不作詳細介紹。
對於樹的旋轉,能保持不變的只有原樹的搜索性質,而原樹的紅黑性質則不能保持,
在紅黑樹的數據插入和刪除後可利用旋轉和顏色重塗來恢復樹的紅黑性質。
至於有些書如 STL源碼剖析有對雙旋的描述,其實雙旋只是單旋的兩次應用,並沒有新的內容,
所以這裏就再也不介紹了,並且左右旋也是相互對稱的,只要理解其中一種旋轉就能夠了。
3、紅黑樹插入、刪除操做的具體實現
3、I、ok,接下來,我們來具體瞭解紅黑樹的插入操做。
向一棵含有n個結點的紅黑樹插入一個新結點的操做能夠在O(lgn)時間內完成。
算法導論:
RB-INSERT(T, z)
1 y ← nil[T]
2 x ← root[T]
3 while x ≠ nil[T]
4 do y ← x
5 if key[z] < key[x]
6 then x ← left[x]
7 else x ← right[x]
8 p[z] ← y
9 if y = nil[T]
10 then root[T] ← z
11 else if key[z] < key[y]
12 then left[y] ← z
13 else right[y] ← z
14 left[z] ← nil[T]
15 right[z] ← nil[T]
16 color[z] ← RED
17 RB-INSERT-FIXUP(T, z)
我們來具體分析下,此段代碼:
RB-INSERT(T, z),將z插入紅黑樹T 以內。
爲保證紅黑性質在插入操做後依然保持,上述代碼調用了一個輔助程序RB-INSERT-FIXUP
來對結點進行從新着色,並旋轉。
14 left[z] ← nil[T]
15 right[z] ← nil[T] //保持正確的樹結構
第16行,將z着爲紅色,因爲將z着爲紅色可能會違背某一條紅黑樹的性質,
因此,在第17行,調用RB-INSERT-FIXUP(T,z)來保持紅黑樹的性質。
RB-INSERT-FIXUP(T, z),以下所示:
1 while color[p[z]] = RED
2 do if p[z] = left[p[p[z]]]
3 then y ← right[p[p[z]]]
4 if color[y] = RED
5 then color[p[z]] ← BLACK ▹ Case 1
6 color[y] ← BLACK ▹ Case 1
7 color[p[p[z]]] ← RED ▹ Case 1
8 z ← p[p[z]] ▹ Case 1
9 else if z = right[p[z]]
10 then z ← p[z] ▹ Case 2
11 LEFT-ROTATE(T, z) ▹ Case 2
12 color[p[z]] ← BLACK ▹ Case 3
13 color[p[p[z]]] ← RED ▹ Case 3
14 RIGHT-ROTATE(T, p[p[z]]) ▹ Case 3
15 else (same as then clause
with "right" and "left" exchanged)
16 color[root[T]] ← BLACK
ok,參考一網友的言論,用本身的語言,再來具體解剖下上述倆段代碼。
爲了保證闡述清晰,我再寫下紅黑樹的5個性質:
1)每一個結點要麼是紅的,要麼是黑的。
2)根結點是黑的。
3)每一個葉結點,即空結點(NIL)是黑的。
4)若是一個結點是紅的,那麼它的倆個兒子都是黑的。
5)對每一個結點,從該結點到其子孫結點的全部路徑上包含相同數目的黑結點。
在對紅黑樹進行插入操做時,咱們通常老是插入紅色的結點,由於這樣能夠在插入過程當中儘可能避免對樹的調整。
那麼,咱們插入一個結點後,可能會使原樹的哪些性質改變列?
因爲,咱們是按照二叉樹的方式進行插入,所以元素的搜索性質不會改變。
若是插入的結點是根結點,性質2會被破壞,若是插入結點的父結點是紅色,則會破壞性質4。
所以,總而言之,插入一個紅色結點只會破壞性質2或性質4。
咱們的回覆策略很簡單,
其1、把出現違背紅黑樹性質的結點向上移,若是能移到根結點,那麼很容易就能經過直接修改根結點來恢復紅黑樹的性質。直接經過修改根結點來恢復紅黑樹應知足的性質。
其2、窮舉全部的可能性,以後把能歸於同一類方法處理的歸爲同一類,不能直接處理的化歸到下面的幾種狀況,
//注:如下狀況三、四、5與上述算法導論上的代碼RB-INSERT-FIXUP(T, z),相對應:
狀況1:插入的是根結點。
原樹是空樹,此狀況只會違反性質2。
對策:直接把此結點塗爲黑色。
狀況2:插入的結點的父結點是黑色。
此不會違反性質2和性質4,紅黑樹沒有被破壞。
對策:什麼也不作。
狀況3:當前結點的父結點是紅色且祖父結點的另外一個子結點(叔叔結點)是紅色。
此時父結點的父結點必定存在,不然插入前就已不是紅黑樹。
與此同時,又分爲父結點是祖父結點的左子仍是右子,對於對稱性,咱們只要解開一個方向就能夠了。
在此,咱們只考慮父結點爲祖父左子的狀況。
同時,還能夠分爲當前結點是其父結點的左子仍是右子,可是處理方式是同樣的。咱們將此歸爲同一類。
對策:將當前節點的父節點和叔叔節點塗黑,祖父結點塗紅,把當前結點指向祖父節點,重新的當前節點從新開始算法。
針對狀況3,變化前(圖片來源:saturnman)[插入4節點]:
變化後:
狀況4:當前節點的父節點是紅色,叔叔節點是黑色,當前節點是其父節點的右子
對策:當前節點的父節點作爲新的當前節點,以新當前節點爲支點左旋。
以下圖所示,變化前[插入7節點]:
變化後:
狀況5:當前節點的父節點是紅色,叔叔節點是黑色,當前節點是其父節點的左子
解法:父節點變爲黑色,祖父節點變爲紅色,在祖父節點爲支點右旋
以下圖所示[插入2節點]
變化後:
==================
3、II、ok,接下來,我們最後來了解,紅黑樹的刪除操做:
算法導論一書,給的算法實現:
RB-DELETE(T, z) 單純刪除結點的總操做
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 3≠ 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
RB-DELETE-FIXUP(T, 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 1
6 color[p[x]] ← RED ▹ Case 1
7 LEFT-ROTATE(T, p[x]) ▹ Case 1
8 w ← right[p[x]] ▹ Case 1
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
13 then color[left[w]] ← BLACK ▹ Case 3
14 color[w] ← RED ▹ Case 3
15 RIGHT-ROTATE(T, w) ▹ Case 3
16 w ← right[p[x]] ▹ Case 3
17 color[w] ← color[p[x]] ▹ Case 4
18 color[p[x]] ← BLACK ▹ Case 4
19 color[right[w]] ← BLACK ▹ Case 4
20 LEFT-ROTATE(T, p[x]) ▹ Case 4
21 x ← root[T] ▹ Case 4
22 else (same as then clause with "right" and "left" exchanged)
23 color[x] ← BLACK
爲了保證如下的介紹與闡述清晰,我第三次重寫下紅黑樹的5個性質
1)每一個結點要麼是紅的,要麼是黑的。
2)根結點是黑的。
3)每一個葉結點,即空結點(NIL)是黑的。
4)若是一個結點是紅的,那麼它的倆個兒子都是黑的。
5)對每一個結點,從該結點到其子孫結點的全部路徑上包含相同數目的黑結點。
(相信,重述了3次,你應該有深入記憶了。:D)
saturnman:
紅黑樹刪除的幾種狀況:
-------------------------------------------------
博主提醒:
如下全部的操做,是針對紅黑樹已經刪除結點以後,
爲了恢復和保持紅黑樹原有的5點性質,所作的恢復工做。
前面,我已經說了,由於插入、或刪除結點後,
可能會違背、或破壞紅黑樹的原有的性質,
因此爲了使插入、或刪除結點後的樹依然維持爲一棵新的紅黑樹,
那就要作倆方面的工做:
一、部分結點顏色,從新着色
二、調整部分指針的指向,即左旋、右旋。
而下面全部的文字,則是針對紅黑樹刪除結點後,所作的修復紅黑樹性質的工做。
二零一一年一月七日更新。
------------------------------------------------------------
(注:如下的狀況三、四、五、6,與上述算法導論之代碼RB-DELETE-FIXUP(T, x) 恢復與保持
中case1,case2,case3,case4相對應。)
狀況1:當前節點是紅色
解法,直接把當前節點染成黑色,結束。
此時紅黑樹性質所有恢復。
狀況2:當前節點是黑色且是根節點
解法:什麼都不作,結束
狀況3:當前節點是黑色,且兄弟節點爲紅色(此時父節點和兄弟節點的子節點分爲黑)。
解法:把父節點染成紅色,把兄弟結點染成黑色,以後從新進入算法(咱們只討論當前節點是其父節點左孩子時的狀況)。
而後,針對父節點作一次左旋。此變換後原紅黑樹性質5不變,而把問題轉化爲兄弟節點爲黑色的狀況。
3.變化前:
3.變化後:
狀況4:當前節點是黑色,且兄弟是黑色,且兄弟節點的兩個子節點全爲黑色。
解法:把當前節點和兄弟節點中抽取一重黑色追加到父節點上,把父節點當成新的當前節點,從新進入算法。(此變換後性質5不變)
4.變化前
4.變化後
狀況5:當前節點顏色是黑色,兄弟節點是黑色,兄弟的左子是紅色,右子是黑色。
解法:把兄弟結點染紅,兄弟左子節點染黑,以後再在兄弟節點爲支點解右旋,
以後從新進入算法。此是把當前的狀況轉化爲狀況6,而性質5得以保持。
5.變化前:
5.變化後:
狀況6:當前節點顏色是黑色,它的兄弟節點是黑色,可是兄弟節點的右子是紅色,兄弟節點左子的顏色任意。
解法:把兄弟節點染成當前節點父節點的顏色,把當前節點父節點染成黑色,兄弟節點右子染成黑色,
以後以當前節點的父節點爲支點進行左旋,此時算法結束,紅黑樹全部性質調整正確。
6.變化前:
6.變化後:
限於篇幅,再也不過多贅述。更多,可參考算法導論或下文我寫的第二篇文章。
完。
July、二零一零年十二月二十九日初稿。三十日凌晨修訂。行文3個小時以上。
----------------
今下午畫紅黑樹畫了好幾個鐘頭,貼倆張圖:
紅黑樹插入的3種狀況:
紅黑樹刪除的4種狀況:
ok,只貼倆張,更多,參考我寫的關於紅黑樹的第二篇文章:
紅黑樹算法的層層剖析與逐步實現[推薦]
http://blog.csdn.net/v_JULY_v/archive/2010/12/31/6109153.aspx
這篇文章針對算法實現源碼分十層,層層、逐層剖析,相信,更清晰易懂。
或者,saturnman的這篇文章: http://saturnman.blog.163.com/blog/static/5576112010969420383/。
July、二零一零年十二月三十一日、最後更新。