紅黑樹是一種具有紅黑性質(zhì)的平衡樹。在前面章節(jié)中我們有講到過另外一種平衡樹。
AVL樹:http://www.lxweimin.com/p/fa3858234329
AVL樹講究完全平衡。一旦左右子樹高度相差2,就需要旋轉(zhuǎn)來達(dá)到平衡。旋轉(zhuǎn)的次數(shù)是O(h)。和高度相等。
紅黑樹就厲害了,只要達(dá)到近似平衡,黑高度(從根節(jié)點(diǎn)到葉子節(jié)點(diǎn)的黑色節(jié)點(diǎn)數(shù)目)左子樹和右子樹相同。
紅黑樹的基本概念和性質(zhì)
哨兵節(jié)點(diǎn)
如果一個(gè)節(jié)點(diǎn)沒有子節(jié)點(diǎn)或者沒有父節(jié)點(diǎn),則該節(jié)點(diǎn)指針屬性是NIL 。
一個(gè)紅黑樹是滿足下列性質(zhì)的BST:
- (1)每個(gè)節(jié)點(diǎn)或者是黑色,或者是紅色。
- (2)根節(jié)點(diǎn)是黑色。
- (3)每個(gè)葉子節(jié)點(diǎn)(NIL)是黑色。
- (4)如果一個(gè)節(jié)點(diǎn)是紅色的,則它的子節(jié)點(diǎn)必須是黑色的。
- (5)從一個(gè)節(jié)點(diǎn)到該節(jié)點(diǎn)的子孫節(jié)點(diǎn)的所有路徑上包含相同數(shù)目的黑節(jié)點(diǎn)。
簡單概括: 黑根,紅黑黑,黑高相同
一顆完整的紅黑樹,如下圖所示:
image.png
紅黑樹,和AVL樹的區(qū)別,就在于,它總是希望通過,簡單的改變顏色,就使這棵樹,達(dá)到黑高度相同。從而平衡。實(shí)在不行就通過旋轉(zhuǎn)。所以紅黑樹的平衡方法,改變顏色
,旋轉(zhuǎn)
。
左旋和右旋
根據(jù)上圖,我們按照《算法導(dǎo)論》的偽代碼進(jìn)行逐行解析源碼
//按照從下往上不斷平衡的原則,先搞定X的指針變化,然后在搞定Y的指針變化,最后把X,Y指針相連
//p 是父節(jié)點(diǎn)的意思,NIL 是空節(jié)點(diǎn)的意思,left和right分別是左節(jié)點(diǎn)和右節(jié)點(diǎn)的意思
//1 首先 旋轉(zhuǎn)完的x的right x 的右節(jié)點(diǎn)指針指向y的左節(jié)點(diǎn)【b】
//2 旋轉(zhuǎn)完的【b】的父親改變了, y.left.p = x; 【b】的父親變成x
//3 旋轉(zhuǎn)完的 Y的父節(jié)點(diǎn)也改變了,
//4 【R】節(jié)點(diǎn)的子節(jié)點(diǎn)改變了 對(duì)應(yīng)這段代碼 x.p.left = y x.p.right = y ;
//5 最后把他兩個(gè)相連 y.left = x; x.p =y;
LEFT-ROTATE(T, x){
y = x.right // set y 設(shè)置y是x的右子樹
x.right = y.left // 【x】的右節(jié)點(diǎn)指針指向y的左節(jié)點(diǎn)【b】
if (y.left != T.NIL){ //如果y的左子樹不空 【b】不是空 ,就是 【b】的父親
y.left.p = x; //【b】的父親改成X
}
y.p = x.p ; //Y的父親是原來X的父親
if(x.p ==T.NIL){ //如果x的父親是空的,說明x是根節(jié)點(diǎn)。
T.root = y ; //那么這個(gè)時(shí)候,這棵樹的根節(jié)點(diǎn),變成Y
}else if( x.p.left ==x ){ //如果【x】是【R】的左節(jié)點(diǎn)
x.p.left = y ; //那么改成【R】的左節(jié)點(diǎn)是【Y】
}else{ //如果【x】是【R】的右節(jié)點(diǎn)
x.p.right = y ; //那么改成【R】的右節(jié)點(diǎn)是【Y】
}
y.left = x;
x.p =y;
}
主要步驟就是
- 先找到旋轉(zhuǎn)反方向的子節(jié)點(diǎn)【Y】,作為固定點(diǎn),左旋就是逆時(shí)針旋轉(zhuǎn)【X】,右旋就是順時(shí)針旋轉(zhuǎn)【X】。旋轉(zhuǎn)之后,從下到上,從X===【b】==》Y===》R(右旋轉(zhuǎn)正好左右調(diào)換)逐漸改變指針。使得指針指向正確的節(jié)點(diǎn)。右旋正好鏡像調(diào)換,所以就不寫了。
jdk源碼中TreeMap
是紅黑樹的實(shí)現(xiàn)。還有類似的TreeSet
(底層是用TreeMap的Key來保存 Set 集合的元素)
,下面我們來看看,TreeMap中的左旋,右旋代碼:
private void rotateLeft(Entry<K,V> p) {
if (p != null) {
Entry<K,V> r = p.right;
p.right = r.left;
if (r.left != null)
r.left.parent = p;
r.parent = p.parent;
if (p.parent == null)
root = r;
else if (p.parent.left == p)
p.parent.left = r;
else
p.parent.right = r;
r.left = p;
p.parent = r;
}
}
p對(duì)應(yīng)偽代碼中的【x】,【r】對(duì)應(yīng)【Y】,簡直是一模一樣的代碼
(過分)。
紅黑樹的插入操作:
上偽代碼:
RB-INSERT(T, z){
y = T.NIL ; // 新建節(jié)點(diǎn)“y”,將y設(shè)為空節(jié)點(diǎn)。
x = T.root ; // 設(shè)“紅黑樹T”的根節(jié)點(diǎn)為“x”
while(x!= T.NIL){ //先循環(huán)遍歷,從根節(jié)點(diǎn)不斷往下找,直到找到插入的位置,
//插入之后,肯定是葉子節(jié)點(diǎn),所以x =T.NIL ,while條件不滿足,跳出循環(huán)
y =x ; //最后是插入位置Y,也就是說,插入的節(jié)點(diǎn)的父節(jié)點(diǎn)是【y】
if(z.key<x.key){
x =x.left ;
}else{
x = x.right;
}
}
z.p = y ; // 設(shè)置 “z的父親” 為 “y”
if(y==T.NIL){
T.root =z ; // 若父節(jié)點(diǎn)【y】是空節(jié)點(diǎn),則將【z】設(shè)為【根節(jié)點(diǎn)】
}else if(z.key <y.key){ // 若【z】小于父節(jié)點(diǎn)【y】 那,就是y 的左節(jié)點(diǎn)
y.left =z ;
}else{
y.right = z; // 若【z】大于父節(jié)點(diǎn)【y】 那,就是y 的右節(jié)點(diǎn)
}
z.left =T.NIL ;
z.right =T.NIL ;
z.color = RED ;
RB-INSERT-FIXUP(T, z) // 通過RB-INSERT-FIXUP對(duì)紅黑樹的節(jié)點(diǎn)進(jìn)行顏色修改以及旋轉(zhuǎn),
// 讓樹T仍然是一顆紅黑樹
}
基本步驟就是先找到插入位置【y】,這個(gè)作為插入節(jié)點(diǎn)【z】的父節(jié)點(diǎn)。再判斷是 【y】節(jié)點(diǎn)的左節(jié)點(diǎn)還是右節(jié)點(diǎn),最后,插入的時(shí)候是葉子節(jié)點(diǎn),所以left.right都是空節(jié)點(diǎn)NIL。剛開始插入為了滿足規(guī)則(5).不破壞黑平衡,所以先插入的節(jié)點(diǎn),肯定是紅色的。
再看TreeMap的實(shí)現(xiàn),沒有傳入Comparator的時(shí)候
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value); //如果map中原來有的話,替換原來的值,直接return
} while (t != null);
}
//parent 就相當(dāng)于偽代碼中的y ,新建一個(gè)節(jié)點(diǎn)Entry
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
可以發(fā)現(xiàn)代碼差不多,也是先查找插入位置,然后在和父節(jié)點(diǎn)比較大小,確定是父節(jié)點(diǎn)的左節(jié)點(diǎn)還是右節(jié)點(diǎn)。
插入操作之后的平衡操作
- 情況說明:被插入的節(jié)點(diǎn)是根節(jié)點(diǎn)。
處理方法:直接把此節(jié)點(diǎn)涂為黑色。 - 情況說明:被插入的節(jié)點(diǎn)的父節(jié)點(diǎn)是黑色。
處理方法:什么也不需要做。節(jié)點(diǎn)被插入后,仍然是紅黑樹。 - 情況說明:被插入的節(jié)點(diǎn)的父節(jié)點(diǎn)是紅色。
處理方法:那么,該情況與紅黑樹的“特性(5)”相沖突。這種情況下,被插入節(jié)點(diǎn)是一定存在非空祖父節(jié)點(diǎn)的;進(jìn)一步的講,被插入節(jié)點(diǎn)也一定存在叔叔節(jié)點(diǎn)(即使叔叔節(jié)點(diǎn)為空,我們也視之為存在,空節(jié)點(diǎn)本身就是黑色節(jié)點(diǎn))。理解這點(diǎn)之后,我們依據(jù)"叔叔節(jié)點(diǎn)的情況",將這種情況進(jìn)一步劃分為3種情況(Case)。
在下面的偽代碼中,詳細(xì)講解了3種情況,該如何操作,以及為什么這么操作,使得這個(gè)紅黑樹趨于平衡。
3中情況: - 當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)是紅色,且當(dāng)前節(jié)點(diǎn)的祖父節(jié)點(diǎn)的另一個(gè)子節(jié)點(diǎn)(叔叔節(jié)點(diǎn))也是紅色。
- 當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)是紅色,叔叔節(jié)點(diǎn)是黑色,且當(dāng)前節(jié)點(diǎn)是其父節(jié)點(diǎn)的右孩子
- 當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)是紅色,叔叔節(jié)點(diǎn)是黑色,且當(dāng)前節(jié)點(diǎn)是其父節(jié)點(diǎn)的左孩子
//z是插入之后的節(jié)點(diǎn),而且已經(jīng)顏色變成紅色的了
RB-INSERT-FIXUP(T, z){
while(z.p.color ==RED){
if(z.p ==z.p.p.left) { //如果z的父節(jié)點(diǎn)是祖父節(jié)點(diǎn)的左孩子
y = z.p.p.right ; //y是z的叔叔節(jié)點(diǎn)
//CASE1 =====================叔叔是紅色(前提條件父親節(jié)點(diǎn)是紅色的)
/**
* CASE 1
* 如果叔叔也是紅色的,那么同時(shí)把叔叔和父親變成黑色的,
* 然后祖父節(jié)點(diǎn)變成紅色的,就能使,這棵小紅黑樹,黑平衡
* 因?yàn)槭迨搴透赣H,同時(shí)+1 ,祖父-1 ,經(jīng)過z的黑高度相當(dāng)于沒變。
*/
if(y.color ==RED){
z.p.color =BLACK ;
y.color = BLACK;
z.p.p.color = RED ;
z = z.p.p ; //然后祖父節(jié)點(diǎn),變成新的當(dāng)前節(jié)點(diǎn),就是說,
//z節(jié)點(diǎn)到祖父節(jié)點(diǎn),這棵小紅黑樹,已經(jīng)黑平衡了,從下往上逐漸通過
//變色或者旋轉(zhuǎn)來使整個(gè)樹,黑平衡。
/**
* CASE 2 (條件 y.color =BLACK && z == z.p.right)
* 既然不屬于CASE1 ,那么 y.color ==RED 不成立,那就是說 y 是黑色的,叔叔節(jié)點(diǎn)是黑色的
* z是右節(jié)點(diǎn), 看下圖中 【z】節(jié)點(diǎn) =【007】 【z.p】z父親節(jié)點(diǎn) =【002】 【y】z叔叔節(jié)點(diǎn) =【014】
* 之前 我們說過,紅黑樹新增的時(shí)候,【核心思想】:【要不斷的將破壞紅黑樹特性的紅色節(jié)點(diǎn)上移(即向根方向移動(dòng))】
* 那么應(yīng)該是【002】節(jié)點(diǎn)為固定點(diǎn),然后【007】節(jié)點(diǎn)逆時(shí)針旋轉(zhuǎn)(左旋),把【007】轉(zhuǎn)到【002】和【011】中間
* ,旋轉(zhuǎn)完發(fā)現(xiàn),【002】和【007】連續(xù)兩個(gè)紅節(jié)點(diǎn),不符合紅黑樹特性(4)
*/
}else if(z == z.p.right){
z = z.p ;
LEFT-ROTATE(T, z);
}
/**
* CASE 3 (條件 y.color =BLACK && z == z.p.left)
* z是左節(jié)點(diǎn), 看下圖中 CASE3 【z】節(jié)點(diǎn) =【002】 ,經(jīng)過case2的時(shí)候z = z.p 使得z是【002】節(jié)點(diǎn)
* 首先嘗試,改變顏色直接到達(dá)黑平衡。吧【z.p】【007】節(jié)點(diǎn)變成紅色,那經(jīng)過【007】節(jié)點(diǎn)的黑色-1 .那么
* 把【011】節(jié)點(diǎn)變成黑色,+1。使得黑高度正負(fù)抵消,但是【011】到【015】這條路徑,黑高度又-1 ,然后通過
* 【007】位固定點(diǎn),順時(shí)針旋轉(zhuǎn)【011】節(jié)點(diǎn),使得【007】這個(gè)黑色加入到【011】===》【015】這條路中。
*/
z.p.color = BLACK ;
z.p.p.color =RED ;
RIGHT-ROTATE(T, z.p.p);
}else{
same as then clause with "right" and "left" exchanged 正好相反,然后交換left和right就好了
}
}
root.color = BLACK; 在最后 root根節(jié)點(diǎn),變成黑色的根節(jié)點(diǎn)
}
下圖是,3種情況,如何改變顏色,或者旋轉(zhuǎn)使得趨于黑平衡。
TreeMap中的fixAfterInsertion方法,和上面的偽代碼,實(shí)現(xiàn)一模一樣 。
紅黑樹的刪除操作
紅黑樹的復(fù)雜主要在于刪除操作
主題思路基本同二叉查找樹,首先查找到這個(gè)節(jié)點(diǎn)z,然后就是執(zhí)行RB-DELETE算法,當(dāng)結(jié)點(diǎn)z有至多一個(gè)兒子時(shí),讓y=z,然后刪除結(jié)點(diǎn)y用結(jié)點(diǎn)y的兒子x(x可以是nil,這時(shí)nil.parent可指向z.parent,體現(xiàn)了哨兵的作用)去取代結(jié)點(diǎn)y,z有兩個(gè)兒子時(shí),刪除結(jié)點(diǎn)z的后繼y,并用y的唯一的兒子結(jié)點(diǎn)x取代y
上刪除操作的偽代碼:
/**
* 主題思路基本同二叉查找樹,首先查找到這個(gè)節(jié)點(diǎn)z,
* 然后就是執(zhí)行RB-DELETE算法,先當(dāng)做BST刪除掉節(jié)點(diǎn),刪除節(jié)點(diǎn),需要改變指針,或者需要后繼賦值
* 算法描述:
* 當(dāng)結(jié)點(diǎn)z有至多一個(gè)兒子時(shí),
* 讓y=z,然后刪除結(jié)點(diǎn)y用結(jié)點(diǎn)y的兒子x(x可以是nil,
* 這時(shí)nil.parent可指向z.parent,體現(xiàn)了哨兵的作用)去取代結(jié)點(diǎn)y,z有兩個(gè)兒子時(shí),
* 刪除結(jié)點(diǎn)z的后繼y,并用y的唯一的兒子結(jié)點(diǎn)x取代y
*/
RB-DELETE(T, z){
if(z.left ==T.NIL || z.right ==T.NIL){
y= z;
}else{
y = TREE-SUCCESSOR(z); // TREE-SUCCESSOR可以是左子樹的最大值,或者右子樹的最小值.
// 【注意:】 后繼只可能最多有一個(gè)孩子,因?yàn)榧僭O(shè)TREE-SUCCESSOR算法取得是右子樹的最小值.
// 那么假如左右子樹都有,那左子樹肯定比這個(gè)后繼節(jié)點(diǎn)小,所以所謂的后繼就不是后繼了,取左子樹最大值同理
//所以下面是分兩種情況
}
if(y.left != T.NIL){
x = y.left;
}else{
x = y.right ;
}
/**
* x是y的替換品,那么x肯定是個(gè)葉子節(jié)點(diǎn),只需要改變x的parent, 和y的父親節(jié)點(diǎn)的指針,
* y如果是y父親節(jié)點(diǎn)的左孩子,就if(y.p.left ==y) y.p.left = x; 父親節(jié)點(diǎn)的左孩子指向x(y的替換品)
* y如果是y父親節(jié)點(diǎn)的右孩子,就if(y.p.right ==y) y.p.right = x; 父親節(jié)點(diǎn)的右孩子指向x(y的替換品)
*/
x.p = y.p ; //x的parent替換為y的parent 本來是y.p===>y===>x,把y干掉之后,y.p===>x,也就是x.p = y.p
/**
* CASE1 y(被刪除的節(jié)點(diǎn))的父親節(jié)點(diǎn)是空,那么y就是根節(jié)點(diǎn),z也是根節(jié)點(diǎn),刪除
* 之后,x頂替,成為root節(jié)點(diǎn)
*/
if(y.p ==T.NIL){
T.root = x ;
/**
* CASE2 y(被刪除的節(jié)點(diǎn))是父親節(jié)點(diǎn)的左孩子,x頂替為y父親節(jié)點(diǎn)的左孩子
*
*/
}else if(y.p.left ==y){
y.p.left = x; //那么x替換成y.p.left x是y一個(gè)替換品
}else{
y.p.right = x; //那么x替換成y.p.right
}
if( y!= z){
z.key = y.key; //【y】節(jié)點(diǎn)的值 賦值給 【z】節(jié)點(diǎn)
}
//最后
if(y.color == BLACK){
RB-DELETE-FIXUP(T, x)
}
}
下面,我們來一個(gè)例子,來逐行校對(duì),上面的偽代碼。
圖0: 原圖: 需要?jiǎng)h除節(jié)點(diǎn)7,根節(jié)點(diǎn)
圖1:把y(刪除的節(jié)點(diǎn))的值,賦值給z,
圖2: 賦值過去之后,根節(jié)點(diǎn)是數(shù)字5, 然后2節(jié)點(diǎn)的右節(jié)點(diǎn),指向替代品4,4的父親指針指向原來y(5號(hào)節(jié)點(diǎn))的父親2號(hào)節(jié)點(diǎn)。
圖3:指針變換結(jié)束
刪除之后的平衡操作。
首先上偽代碼,大家跟著偽代碼一步步理解。
/**
* 紅黑樹刪除節(jié)點(diǎn)后,重新平衡后,可能有多種平衡方法,平衡之后,樹結(jié)構(gòu)也有多樣的
* 承接上面的RB-DELETE ,x是實(shí)際被刪除節(jié)點(diǎn)【y】的替代品,承接圖3,x 就是4號(hào)節(jié)點(diǎn),是紅色的,直接走最后一步,把x變成黑色,就好了
*然后圖0 ,y節(jié)點(diǎn)原來是黑色的,那么根結(jié)點(diǎn)到x結(jié)點(diǎn)的路徑中的黑色結(jié)點(diǎn)數(shù)目將會(huì)減少1,需要執(zhí)行RB-DELETE-FIXUP,恢復(fù)黑平衡
* 【既然刪除y(y是黑色),意味著減少一個(gè)黑色節(jié)點(diǎn);那么,再在該位置上增加一個(gè)黑色即可。
* 這樣,當(dāng)我們假設(shè)"x包含一個(gè)額外的黑色",就正好彌補(bǔ)了"刪除y所丟失的黑色節(jié)點(diǎn)"】
* 加上黑色之后,就會(huì)出現(xiàn)下面3種情況:
* ① 情況說明:x是“紅+黑”節(jié)點(diǎn)。
* 處理方法:直接把x設(shè)為黑色,結(jié)束。此時(shí)紅黑樹性質(zhì)全部恢復(fù)。
* ② 情況說明:x是“黑+黑”節(jié)點(diǎn),且x是根。
* 處理方法:什么都不做,結(jié)束。此時(shí)紅黑樹性質(zhì)全部恢復(fù)。
* ③ 情況說明:x是“黑+黑”節(jié)點(diǎn),且x不是根。
* 處理方法:這種情況又可以劃分為4種子情況。這4種子情況
* 總共有四種Case ,其中CASE1 ====》 可以轉(zhuǎn)化為CASE2 ,也可以轉(zhuǎn)化成case3 ,
* CASE3 ====》 可以轉(zhuǎn)化為CASE4 ;
*/
RB-DELETE-FIXUP(T, x){
while(x!=root && x.color ==BLACK){
if(x == x.p.left){
w = x.p.right ; //w 是x的兄弟節(jié)點(diǎn)
/**
* CASE1 x是“黑+黑”節(jié)點(diǎn),x的兄弟節(jié)點(diǎn)是紅色。
* 因?yàn)椤究偟乃悸肥前讯嘤嗟暮谏?jié)點(diǎn)往上移動(dòng)】
* case1 左旋轉(zhuǎn)使得兄弟節(jié)點(diǎn)是黑色的。
* 只有這樣子,才能先x父節(jié)點(diǎn)的黑色+1 (x的黑節(jié)點(diǎn),移動(dòng)其中一個(gè)到父節(jié)點(diǎn),【往上移動(dòng)】),
* 然后x的兄弟節(jié)點(diǎn)黑色-1 ,變成紅色。
*
*/
if(w.color ==RED){
w.color = BLACK;
x.p.color = RED;
LEFT-ROTATE(T, x.p);
w = x.p.right;
}
/**
* case 2 x是“黑+黑”節(jié)點(diǎn),x的兄弟節(jié)點(diǎn)是黑色。x的兄弟節(jié)點(diǎn)的兩個(gè)孩子都是黑色
* 將“x中多余的一個(gè)黑色屬性上移(往根方向移動(dòng))”
* x是“黑+黑”節(jié)點(diǎn),我們將x由“黑+黑”節(jié)點(diǎn) 變成 “黑”節(jié)點(diǎn),多余的一個(gè)“黑”屬性移到x的父節(jié)點(diǎn)中,
* 即x的父節(jié)點(diǎn)多出了一個(gè)黑屬性(若x的父節(jié)點(diǎn)原先是“黑”,則此時(shí)變成了“黑+黑”;
* 若x的父節(jié)點(diǎn)原先時(shí)“紅”,則此時(shí)變成了“紅+黑”)。
* 此時(shí),需要注意的是:所有經(jīng)過x的分支中黑節(jié)點(diǎn)個(gè)數(shù)沒變化;
* 但是,所有經(jīng)過x的兄弟節(jié)點(diǎn)的分支中黑色節(jié)點(diǎn)的個(gè)數(shù)增加了1(因?yàn)閤的父節(jié)點(diǎn)多了一個(gè)黑色屬性)!
* 為了解決這個(gè)問題,我們需要將“所有經(jīng)過x的兄弟節(jié)點(diǎn)的分支中黑色節(jié)點(diǎn)的個(gè)數(shù)減1”即可,
* 那么就可以通過“將x的兄弟節(jié)點(diǎn)由黑色變成紅色”來實(shí)現(xiàn)。
* 經(jīng)過上面的步驟(將x的兄弟節(jié)點(diǎn)設(shè)為紅色),
* 多余的一個(gè)顏色屬性(黑色)已經(jīng)跑到x的父節(jié)點(diǎn)中。
* 我們需要將x的父節(jié)點(diǎn)設(shè)為“新的x節(jié)點(diǎn)”進(jìn)行處理。
* 若“新的x節(jié)點(diǎn)”是“黑+紅”,直接將“新的x節(jié)點(diǎn)”設(shè)為黑色,即可完全解決該問題;
* 若“新的x節(jié)點(diǎn)”是“黑+黑”,則需要對(duì)“新的x節(jié)點(diǎn)”進(jìn)行進(jìn)一步處理。
*/
if(w.left.color ==BLACK && w.right.color ==BLACK){
w.color =RED;
x = x.p ;
}else {
/**
* case 3 x是“黑+黑”節(jié)點(diǎn),x的兄弟節(jié)點(diǎn)是黑色。x的兄弟節(jié)點(diǎn)的左孩子是紅色,右孩子是黑色的
* case 3是為了case4 做準(zhǔn)備的,總而言之,case3變化之后,得呈現(xiàn)這個(gè)樣子:
*
*/
if(w.left.color ==RED && w.right.color ==BLACK){
w.left.color = BLACK;
w.color =RED;
RIGHT-ROTATE(T, w)
w =x.p.right;
}
/**
* case 4 比較復(fù)雜,在外面解釋
*
*/
w.color = x.p.color;
x.p.color =BLACK ;
w.right.color =BLACK;
LEFT-ROTATE(T, x.p);
x = root;
}
}else{
same as then clause with "right" and "left" exchanged
}
}
x.color =BLACK;
}
用一個(gè)紅黑樹的例子來講解。
先上原圖:
圖0:
注意:5節(jié)點(diǎn)后面還有兩個(gè)NIL(節(jié)點(diǎn)),哨兵節(jié)點(diǎn),到后面你會(huì)發(fā)現(xiàn),這個(gè)哨兵節(jié)點(diǎn)是有用的。
接下來,我們刪除0010
節(jié)點(diǎn)。
圖1:
刪除掉之后,我們會(huì)走【RB-DELETE-FIXUP】這個(gè)算法,來讓樹保持黑平衡。
case 1 就是圖1
case1 : *x是"黑+黑"節(jié)點(diǎn),x的兄弟節(jié)點(diǎn)是紅色。(此時(shí)x的父節(jié)點(diǎn)和x的兄弟節(jié)點(diǎn)的子節(jié)點(diǎn)都是黑節(jié)點(diǎn))。*
x節(jié)點(diǎn)就是NIL 節(jié)點(diǎn),就是圖中的【NULL LEAF】節(jié)點(diǎn)。從偽代碼分析中我們可以知道,case1和case3 都只是一種中間狀態(tài),都需要后續(xù)的操作,才能真正實(shí)現(xiàn)黑平衡。case2和case4 ,才能實(shí)現(xiàn)黑平衡。因?yàn)橹挥?code>case2和case4 ,把x上面的多余黑色,向上移動(dòng)到父節(jié)點(diǎn)。
接下來,走case1中的偽代碼,w代表兄弟節(jié)點(diǎn),p代表父親,LEFT-ROTATE(T, x.p);代表旋轉(zhuǎn)x的父親節(jié)點(diǎn),也就是【005】節(jié)點(diǎn)。
if(w.color ==RED){
w.color = BLACK;
x.p.color = RED;
LEFT-ROTATE(T, x.p);
w = x.p.right;
}
經(jīng)過變化之后,如下圖:
圖3 :
case 3 x是“黑+黑”節(jié)點(diǎn),x的兄弟節(jié)點(diǎn)是黑色。x的兄弟節(jié)點(diǎn)的左孩子是紅色,右孩子是黑色的
case 3是為了case4 做準(zhǔn)備的,,在走case3 的偽代碼:
if(w.left.color ==RED && w.right.color ==BLACK){
w.left.color = BLACK;
w.color =RED;
RIGHT-ROTATE(T, w)
w =x.p.right;
}
經(jīng)過變化之后,如下圖:
圖4 :
case 4 x是“黑+黑”節(jié)點(diǎn),x的兄弟節(jié)點(diǎn)是黑色;x的兄弟節(jié)點(diǎn)的右孩子是紅色的,x的兄弟節(jié)點(diǎn)的左孩子任意顏色。
【0011】節(jié)點(diǎn)的左孩子是個(gè)NIL(黑色)節(jié)點(diǎn)。
case 4 涉及到3條路徑的黑高度變化。
在走case4 的偽代碼:
w.color = x.p.color; //上圖中就是把5號(hào)節(jié)點(diǎn)的顏色,給兄弟節(jié)點(diǎn)【0011】節(jié)點(diǎn)
x.p.color =BLACK ;
w.right.color =BLACK;
LEFT-ROTATE(T, x.p);
x = root;
case 4 完整的一個(gè)操作如下圖:
圖4-2 :
- 首先旋轉(zhuǎn)之后,把 x.p.color =BLACK ; 把父親節(jié)點(diǎn)的顏色變色黑色,就是【核心思想:把黑加黑中的某個(gè)黑色,向上移動(dòng)給父親節(jié)點(diǎn)】。
- 但是這樣子的話,根節(jié)點(diǎn)到兄弟節(jié)點(diǎn)的左孩子NULL LEAF,這條路徑多了一個(gè)黑色,然后把0011節(jié)點(diǎn)變成紅色,
- 但是這樣的話,根節(jié)點(diǎn)到兄弟節(jié)點(diǎn)的右孩子0012節(jié)點(diǎn),少了一個(gè)黑色,那就吧0012節(jié)點(diǎn)變成黑色。最終根節(jié)點(diǎn)到3個(gè)NULL LEAF都是黑高度=2 ;
上面這個(gè)紅黑樹,刪除節(jié)點(diǎn)之后,經(jīng)過了3個(gè)情況。最終才完成黑平衡。但是并沒有經(jīng)過case2 .
然后我們?cè)賮砜纯碿ase2如何讓樹黑平衡。
x是"黑+黑"節(jié)點(diǎn),x的兄弟節(jié)點(diǎn)是黑色,x的兄弟節(jié)點(diǎn)的兩個(gè)孩子都是黑色
將“x中多余的一個(gè)黑色屬性上移(往根方向移動(dòng))”。 x是“黑+黑”節(jié)點(diǎn),我們將x由“黑+黑”節(jié)點(diǎn) 變成 “黑”節(jié)點(diǎn),多余的一個(gè)“黑”屬性移到x的父節(jié)點(diǎn)中,即x的父節(jié)點(diǎn)多出了一個(gè)黑屬性(若x的父節(jié)點(diǎn)原先是“黑”,則此時(shí)變成了“黑+黑”;若x的父節(jié)點(diǎn)原先時(shí)“紅”,則此時(shí)變成了“紅+黑”)。 此時(shí),需要注意的是:所有經(jīng)過x的分支中黑節(jié)點(diǎn)個(gè)數(shù)沒變化;但是,所有經(jīng)過x的兄弟節(jié)點(diǎn)的分支中黑色節(jié)點(diǎn)的個(gè)數(shù)增加了1(因?yàn)閤的父節(jié)點(diǎn)多了一個(gè)黑色屬性)!為了解決這個(gè)問題,我們需要將“所有經(jīng)過x的兄弟節(jié)點(diǎn)的分支中黑色節(jié)點(diǎn)的個(gè)數(shù)減1”即可,那么就可以通過“將x的兄弟節(jié)點(diǎn)由黑色變成紅色”來實(shí)現(xiàn)。
經(jīng)過上面的步驟(將x的兄弟節(jié)點(diǎn)設(shè)為紅色),多余的一個(gè)顏色屬性(黑色)已經(jīng)跑到x的父節(jié)點(diǎn)中。我們需要將x的父節(jié)點(diǎn)設(shè)為“新的x節(jié)點(diǎn)”進(jìn)行處理。若“新的x節(jié)點(diǎn)”是“黑+紅”,直接將“新的x節(jié)點(diǎn)”設(shè)為黑色,即可完全解決該問題;若“新的x節(jié)點(diǎn)”是“黑+黑”,則需要對(duì)“新的x節(jié)點(diǎn)”進(jìn)行進(jìn)一步處理。
如下圖2: