紅黑樹是一棵二叉搜索樹,它在每一個結點上增長了一個存儲位來表示結點的顏色,能夠是RED 或 BLACK。經過對任何一條根到葉子的簡單路徑上各個結點的顏色進行約束,紅黑樹確保沒有一條路徑迴避其餘路徑長處2倍,於是是近似平衡的。html
樹的每一個結點包含 5 個屬性:color,key,left,right和p。若是一個結點沒有子結點或者父結點,則該結點相應的指針屬性的值爲NULL。咱們能夠把這些NULL視爲指向二叉搜索樹葉結點的指針,而把帶關鍵字的結點視爲樹的內部結點。算法
一棵紅黑樹是知足下面紅黑性質的二叉搜索樹:數據結構
1.每一個結點或是紅色的,或是黑色的函數
2.根節點是黑色的spa
3.每一個葉結點(NULL)是黑色的指針
4.若是一個結點是紅色的,那麼他的兩個子結點都是黑色的code
5.對於每一個結點,從該結點到其全部後代葉結點的簡單路徑上,包含相同數目的黑色結點htm
這 5 個性質中1,2,4都比較好理解。3與咱們常說的(大部分數據結構書上說的)葉結點有一點點區別,以下圖:對象
那性質5又是什麼意思呢?咱們再來看一個圖:blog
由紅黑樹的 5 個性質可知,上幅圖中左圖是紅黑樹,而右圖非紅黑樹。右圖中知足紅黑樹的性質1.2.3.4,可是不知足性質5:從根節點6(不包括根節點)到各葉結點的簡單路徑上的黑色黑色結點個數並不相等。例如:6-1有2個,而6-8和6-10都是有三個。
這些約束強制了紅黑樹的關鍵性質: 從根到葉子的最長的可能路徑很少於最短的可能路徑的兩倍長。結果是這個樹大體上是平衡的。由於操做好比插入、刪除和查找某個值的最壞狀況時間都要求與樹的高度成比例,這個在高度上的理論上限容許紅黑樹在最壞狀況下都是高效的,而不一樣於普通的二叉查找樹。
要知道爲何這些特性確保了這個結果,注意到屬性4致使了路徑不能有兩個毗連的紅色節點就足夠了。最短的可能路徑都是黑色節點,最長的可能路徑有交替的紅色和黑色節點。由於根據屬性5全部最長的路徑都有相同數目的黑色節點,這就代表了沒有路徑能多於任何其餘路徑的兩倍長。
在不少樹數據結構的表示中,一個節點有可能只有一個子節點,而葉子節點包含數據。用這種範例表示紅黑樹是可能的,可是這會改變一些屬性並使算法複雜。爲此,本文中咱們使用 "nil 葉子" 或"空(null)葉子",如上圖所示,它不包含數據而只充當樹在此結束的指示。這些節點在繪圖中常常被省略,致使了這些樹好像同上述原則相矛盾,而實際上不是這樣。與此有關的結論是全部節點都有兩個子節點,儘管其中的一個或兩個多是空葉子。
由於每個紅黑樹也是一個特化的二叉查找樹,所以紅黑樹上的只讀操做與普通二叉查找樹上的只讀操做相同。然而,在紅黑樹上進行插入操做和刪除操做會致使再也不符合紅黑樹的性質。恢復紅黑樹的屬性須要少許(O(log n))的顏色變動(實際是很是快速的)和不超過三次樹旋轉(對於插入操做是兩次)。雖然插入和刪除很複雜,但操做時間仍能夠保持爲 O(log n) 次。咱們在這隻講講紅黑樹的插入和刪除。
1.插入
下面看看算法導論中給的僞代碼:
1 /* 2 注意如下的T.nil,是一個與普通紅黑樹結點相同的對象。他的color是BLACK,他也是根節點的父節點 3 RB-INSERT(T,z) //向樹T中增長結點z 4 y = T.nil //根節點的父節點 5 x = T.root //根節點 6 while x != T.nil //while循環內是爲了尋找插入結點z的位置 7 y = x //y始終是x的父節點 8 if z.key < x.key 9 x = x.left 10 else 11 x = x.right 12 //跳出while循環以後,說明y結點的某個孩子是T.nil了,能夠插入了! 13 z.p = y //z的父結點是y 14 if y == T.nil //若是y就是 T.nil說明該樹爲空,插入z後,z就是根節點 15 T.root = z 16 else if z.key < y.key //若是z比y結點值小,則插到y的左孩子上 17 y.left = z 18 else 19 y.right = z //不然插到y的右孩子上 20 z.left = T.nil 21 z.right = T.nil //將z的左右孩子都設爲T.nil 22 z.color = RED //z的顏色設爲紅色 23 RB-INSERT-FIXUP(T,Z) //插入一個紅色結點會破壞紅黑樹的性質,須要調整 24 */
好比咱們插入一個值爲3的結點:在RB-INSERT-FIXUP函數執行以前,執行的結果以下圖:
由上圖能夠看出T.nil的做用是充當一個哨兵,它也是一個紅黑樹結點對象,且顏色爲黑色,其餘的值任意!插入3,並將3的顏色塗成紅色以後,有可能會破壞紅黑樹的性質2和4(上圖就破壞了性質5).因此咱們要調用RB-INSERT-FIXUP來保持紅黑樹的性質。RB-INSERT-FIXUP的僞代碼以下:
1 /* 2 如下是實現RB-INSERT-FIXUP(T,Z)僞代碼 3 while z.p.color == RED //由於z自己是紅色,若是他的父結點是紅色那這個循環就要繼續---調節樹 4 if z.p == z.p.p.left //若是z的父親是z祖父的左孩子 5 y = z.p.p.right //令y爲z祖父的右孩子,也就是說y是z的叔叔 6 if y.color == RED //若是y的顏色是紅色 7 z.p.color = BLACK //case 1 既然z是紅色,爲了避免破壞性質4,將z的父節點塗成黑色 8 y.color = BLACK //case 1 同時也要講z的叔叔結點塗成黑色 9 z.p.p.color=RED //case 1 同時將z的祖父結點(y的父節點)塗成紅色 10 z = z.p.p //case 1 令z 等於 z的祖父,循環繼續 11 else if z == z.p.right //若是z是父結點的右孩子 12 z = z.p //case 2 z等於z的父結點 13 LEFT-ROTATE(T,Z) //case 2 右旋 14 z.p.color = BLACK //case 3 將z的父結點顏色塗成黑色 15 z.p.p.color = RED //case 3 將z的祖父結點塗成紅色 16 RIGHT-ROTATE(T,Z.P.P) //case 3 右旋 17 else(same as then clause with 'right' and 'left' exchanged) 18 T.root.color = BLACK 19 */
這裏僞代碼裏面有兩個函數要注意下,LEFT-ROTATE() 和 RIGHT-ROTATE().這個分別是左旋和右旋的函數。左旋和右旋的過程我已經在個人另外一篇博客中用圖解釋的很清楚了:http://www.cnblogs.com/zhuwbox/p/3636783.html。
下面是左旋的僞代碼:
1 /* 2 LEFT-ROTATE(T,x)--參考上圖 3 y = x.right //給y賦值 4 x.right = y.left //將x的右結點指向y的左結點 5 if y.left != T.nil 6 y.left.p = x //設置y左結點的父節點爲x 7 y.p = x.p //y的父結點是x的父節點 8 if x.p == T.nil //若是 x 是根節點 9 T.root = y; 10 elseif x == x.p.left //若是x是父結點的左孩子 11 x.p.left = y; // 12 else x.p.right = y //若是x是父結點的右孩子 13 y.left = x; //y的左孩子是x 14 x.p = y //x的父節點是y 15 */
RB-INSERT-FIXUP要處理的狀況有三種。
a).狀況一:插入結點後的結點z。z和父結點都是紅色,違反性質4.以下圖:
解決方法是:將z的父結點和叔叔結點塗成黑色,而且z的指針沿z樹上升(對應RB-INSERT-FIXUP代碼中的case 1部分)。所得狀況以下圖
b).狀況二:調整後的結點z(此時是7)和父結點(結點2)都是紅色,可是叔叔結點(結點1)是黑色,此時出現狀況二。解決方法:將2做爲根節點T進行左旋。獲得以下圖:
c).狀況三:調整後的結點z(此時是2)和父結點是紅色,可是叔叔結點(8)是黑色。要進行以下操做:將z結點的父結點塗成黑色,將z的祖父結點塗成紅色。再以z的父結點爲根T,做一次右旋轉便可獲得一棵合法的紅黑樹,以下圖:
此時的z的父節點再也不是紅色,退出while循環(若是不退出循環,狀況確定是這三種中的一種)。一棵合法的紅黑樹造成!