介紹
紅黑樹是一種特殊的平衡二叉樹(AVL),可以保證在最壞的情況下,基本動態集合操作的時間復雜度為O(logn)。因此,被廣泛應用於企業級的開發中。
紅黑樹的性質
在一棵紅黑樹中,其每個結點上增加了一個存儲位(屬性color)來表示結點的顏色,且顏色只能是red or black。通過對任何一條從根到葉子的簡單路徑上各個結點的顏色進行約束,紅黑樹確保沒有一條路徑會比其他路徑長出2倍,因而是近似於平衡的。
樹中每個結點包含5個屬性:color、val、lchild、rchild和p(可選)。如果一個結點沒有子結點或父結點,則該結點相應指針屬性的值為NIL。我們可以把這些NIL視為指向二叉搜索樹的葉結點(外部結點)的指針,而把帶關鍵字的結點視為樹的內部結點。
一棵紅黑樹是滿足下面紅黑性質的二叉搜索樹:
- 每個結點或是紅色的,或是黑色的。
- 根結點是黑色的。
- 每個葉結點(NIL)是黑色的。
- 如果一個結點是紅色的,則它的兩個子結點都是黑色的。
- 對每個結點,從該結點到其所有后代葉結點的簡單路徑上,均包含相同數目的黑色結點。
為了便於處理紅黑樹代碼中的邊界條件,使用一個哨兵來代表NIL。對於一棵紅黑樹tree,哨兵NIL是與一個與樹中普通結點有相同屬性的對象。它的color屬性為black,其他屬性可以為任意值。
旋轉
在一棵含有n個關鍵字的紅黑樹上,進行插入和刪除操作,需要的時間復雜度為O(logn),由於這兩個操作,會導致插入和刪除后的樹不滿足紅黑樹的性質。為了維護這些性質,需要改變樹中某些結點的顏色以及指針結構。
指針結構的修改是通過旋轉來完成的,這是一種能保持二叉搜索樹性質的搜索樹局部操作,旋轉分為左旋和右旋。如下圖所示:

下面給出左旋和右旋操作的代碼為:
1 template<typename T> 2 void RedBlackTree<T>::LeftRotation(RedBlackNode<T>* &t){ 3 RedBlackNode<T> *temp = t->rchild; 4 t->rchild = temp->lchild; 5 if(Parent(t)==NIL){ 6 root = temp; 7 } 8 temp->lchild = t; 9 Parent(t)->rchild = temp; 10 } 11 12 template<typename T> 13 void RedBlackTree<T>::RightRotation(RedBlackNode<T>* &t){ 14 RedBlackNode<T> *temp = t->lchild; 15 t->lchild = temp->rchild; 16 if(Parent(t)==NIL){ 17 root = temp; 18 } 19 temp->rchild = t; 20 Parent(t)->lchild = temp; 21 }
左旋和右旋的偽碼可以參考《算法導論》(磚頭書)。
紅黑樹的插入操作
前面說過,在一棵含有n個關鍵字的紅黑樹上,執行插入操作,需要時間復雜度為O(logn)。為了做到這一點,需要往原紅黑樹中插入一個紅色的結點。那么問題來了,為什么插入的是紅色結點,而不是黑色結點呢?我們知道,紅黑樹有五個性質,如果插入紅色結點,則可能會違反性質4,而如果是插入黑色結點,則一定會違反性質5。也就是說,插入紅色結點比插入黑色結點更不容易違反紅黑樹的性質,而違反性質條數越多,相應的要做的調整操作也就越多,導致算法的時間復雜度也就越高,從而影響算法的執行速度。在《數據結構算法與解析》(高一凡著,清華大學出版社)一書中,給出了插入結點為紅色以及插入結點為黑色兩種操作的算法,本文以插入結點為紅色進行講解。
對於一棵紅黑樹來說,插入一個紅色結點,主要有六種情況,其中三種與另外三種是對稱的。這一點取決於插入結點 z 的父親結點是插入結點的祖父結點的左孩子還是右孩子。
下面給出兩種對稱下,所對應的三種情況:
- case1:插入結點 z 的叔結點 y 是紅色的。

上圖顯示了該情況的情形,這種情況實在插入結點z的父結點z.p和其叔結點y都是紅色時發生的。因為插入結點z的祖父結點z.p.p是黑色的,所以將z.p和y都着為黑色,來解決z和z.p都是紅色的問題,而由於性質5的要求,如果只是把z.p和y的顏色改為黑色,則會破壞該性質,因此需要對z.p.p結點的顏色進行調整,以保證性質5的滿足。
但是,性質1調整以后,就一定能維持紅黑樹的性質嗎?我們以z表示新插入的結點,z'表示經過此次操作后的結點,由上述操作可以知道,z'=z.p.p。則經過此次操作后,有以下結果:
-
- 因為這次操作把z.p.p着為紅色,結點z'在下次操作的開始是紅色的。
- 在這次操作中結點z'.p是z.p.p.p,且這個結點的顏色不會改變。如果它是根結點,則在此次迭代之前它是黑色的,且它在下次操作的開頭任然是黑色的。
- 我們也知道,case1保持性質5,而且它也不會引起性質1或性質3的破壞。
如果結點z'在下一次操作開始時是根結點,則在這次操作中case1修正了唯一被破壞的性質4。由於z'是紅色的而且是根結點,所以性質2成為唯一被違反的性質,這是由z'導致的。
如果結點z'在下一次操作開始時不是根結點,則case1不會導致性質2被破壞,case1修正了在這次操作的開始唯一違反的性質4。然后把z’着色為紅色而z'.p不變,因此,如果z'.p是黑色的,則沒有違反性質4,若是z'.p是紅色的,則違反了性質4。
- case2:插入結點 z 的叔結點 y 是黑色的且 z 是一個右孩子。
- case3:插入結點 z 的叔結點 y 是黑色的且 z 是一個左孩子。
在case2和case3中,z的叔結點是黑色的。通過z是z.p的右孩子還是左孩子來區別這兩種情況(叔結點都是黑色,無法在邏輯上進行區別)。對於這兩種情況,如下圖所示:

左圖為case2,右圖為case3
我們發現case2與case3存在某種指針結構上的關系,很明顯二者之間可以通過左旋和右旋操作進行相互轉換。由於z和z.p都是紅色的,因此,旋轉操作對樹的黑高和性質5都無影響。無論怎么進入哪兩種情況,y總是黑色的,否則就要執行case1對應的操作。此外,這種旋轉操作,有個好處是,並不改變旋轉后,z的身份,盡管會導致z的左右孩子身份改變了,但依舊是z.p的孩子。在case3中,我們可以通過改變某些結點的顏色,並作一次右旋,就能保證性質5。這樣,由於在一行中不會再存在有兩個紅色結點,因此,保證了紅黑樹的性質,所有的處理也到此完畢了。如下所示:

可以看到,case2和case3的操作,會最終使得插入結點后的樹,維持紅黑樹的性質。由此,不禁懷疑,這樣的操作能完全保證嗎?答案是肯定的。下面來證明:
- case2讓z指向紅色的z.p。在case2和case3中,z或z的顏色都不會改變,所以,在由case2轉為case3后,這並不會產生其他性質的改變。
- case3把z.p着成黑色,使得如果z.p在下一次操作開始時是根結點,則它是黑色的。
- 和case1一樣,紅黑樹的性質1、3、5都在case2和case3中得以保持。
由於結點z在case2和case3中都不是根結點,因此,性質2未被破壞,這兩種情況因此也不會引起性質2的違反。由此,證明了z.p為z.p.p的左孩子時候,對插入z后的紅黑樹,按照上述調整,可以做到恢復紅黑樹的性質。而當z.p為z.p.p的右孩子時,由於與前面一大情況是對稱的,因此,通過修改left和right的對應,就可實現。而完全實現樹的回復,可以通過while循環來保持。以下是實現樹的插入的代碼:
1 template<typename T> 2 bool RedBlackTree<T>::Insert(T e){ 3 RedBlackNode<T> *p, *f; 4 p = f = NULL; 5 6 if(!searchBST(root, p, e, f)){//not found, need to create, p points to the last node. 7 8 RedBlackNode<T> *s = createNewNode(e); 9 if(root==NULL){ 10 root = s; 11 root->color = "black"; 12 } 13 else{ 14 if(e<p->val){ 15 p->lchild = s; 16 } 17 else{ 18 p->rchild = s; 19 } 20 if(p->color == "red"){//double red node, need to adjust 21 adjustDoubleRed(s, p); 22 } 23 } 24 return true; 25 } 26 else{//node exists. return false 27 return false; 28 } 29 } 30 31 template<typename T> 32 RedBlackNode<T>* RedBlackTree<T>::Parent(RedBlackNode<T>* &t)const{ 33 /* 34 *@Parameter: 35 *q: a queue to save rb-node. 36 *t: a point which points to a node in the RBTree. 37 *father: a point which points to the father node of t. 38 */ 39 queue<RedBlackNode<T>*> q; 40 RedBlackNode<T>* father; 41 if(root!=NULL){ 42 q.push(root); 43 while(!q.empty()){//BFSTraverse to find the father node of t. 44 father = q.front(); 45 q.pop(); 46 if((father->lchild!=NIL&&father->lchild==t)||(father->rchild!=NIL&&father->rchild==t)){ 47 return father; 48 } 49 else{ 50 if(father->lchild!=NIL){ 51 q.push(father->lchild); 52 } 53 if(father->rchild!=NIL){ 54 q.push(father->rchild); 55 } 56 } 57 } 58 } 59 return NIL; //not found, return NIL 60 } 61 62 template<typename T> 63 bool RedBlackTree<T>::searchBST(RedBlackNode<T>* &t, RedBlackNode<T>* &p, T &e, RedBlackNode<T>* f)const{ 64 //在樹中t中遞歸地查找其值等於e的數據,若查找成功,則指針p指向該數據 65 //結點,並返回true,否則指針p指向查找路徑上訪問的最后一個結點以便插入 66 //並返回false,指針f指向p的雙親,其初始調用值為NULL。Insert()調用 67 if(t==NULL||t==NIL){ 68 p = f; 69 return false; 70 } 71 if(e==t->val){ 72 p = t; 73 return true; 74 } 75 else if(e<t->val){ 76 return searchBST(t->lchild, p, e, t); 77 } 78 else{ 79 return searchBST(t->rchild, p, e, t); 80 } 81 } 82 83 template<typename T> 84 void RedBlackTree<T>::LeftRotation(RedBlackNode<T>* &t){ 85 RedBlackNode<T> *temp = t->rchild; 86 t->rchild = temp->lchild; 87 if(Parent(t)==NIL){ 88 root = temp; 89 } 90 temp->lchild = t; 91 Parent(t)->rchild = temp; 92 } 93 94 template<typename T> 95 void RedBlackTree<T>::RightRotation(RedBlackNode<T>* &t){ 96 RedBlackNode<T> *temp = t->lchild; 97 t->lchild = temp->rchild; 98 if(Parent(t)==NIL){ 99 root = temp; 100 } 101 temp->rchild = t; 102 Parent(t)->lchild = temp; 103 } 104 105 template<typename T> 106 void RedBlackTree<T>::adjustDoubleRed(RedBlackNode<T>* &s, RedBlackNode<T>* &p){ 107 /* 108 *@Parameter: 109 *s: rb-node. 110 *p: the father node of s. 111 */ 112 RedBlackNode<T> *y, *gp; 113 while(p->color=="red"){ 114 gp = Parent(p); 115 if(p==gp->lchild){ 116 y = gp->rchild; 117 if(y->color=="red"){//case 1 118 p->color = "black"; 119 y->color = "black"; 120 gp->color = "red"; 121 s = gp; 122 p = Parent(s); 123 } 124 else if(s==p->rchild){//case 2 125 s = p; 126 LeftRotation(p); 127 } 128 else{ 129 p->color = "black"; 130 gp->color = "red"; 131 RightRotation(gp); 132 } 133 } 134 else{ 135 y = gp->lchild; 136 if(y->color=="red"){//case 1 137 p->color = "black"; 138 y->color = "black"; 139 gp->color = "red"; 140 s = gp; 141 p = Parent(s); 142 } 143 else if(s==p->lchild){//case 2 144 s = p; 145 RightRotation(s); 146 } 147 else{ 148 p->color = "black"; 149 gp->color = "red"; 150 LeftRotation(gp); 151 } 152 } 153 } 154 root->color = "black"; 155 }
代碼的操作,與前文圖片所描述的操作相一致。
紅黑樹的刪除操作
由於紅黑樹與BST樹相似,因此,其刪除操作與BST樹在邏輯上是基本一致的,唯一的區別在於,紅黑樹需要對刪除結點后的樹進行調整,使其符合紅黑樹的性質。對於一棵紅黑樹來說,如果先不考慮結點的顏色,刪除一個結點無非是三種情況,這一點與BST樹是一致的,即:
- 被刪除結點沒有左右子結點;
- 被刪除結點僅有一個子節點(左或右都有可能);
- 被刪除結點左右子結點都存在;
根據上述三種情況,可以編寫出BST樹的刪除結點操作的代碼,下面給出BST樹的刪除操作示意圖:



很明顯,紅黑樹在結點的結構上,也是符合上述形式的,即左<根<右,因此,紅黑樹的刪除操作是從BST輸的刪除操作的基礎上,修改得到的,為什么需要修改呢?就是因為紅黑樹的每個結點具有紅黑屬性。
由於紅黑屬性的影響,導致,刪除結點后紅黑樹將不符合紅黑樹原有的特性,我們知道,刪除某個結點,按照上述調整,將會使得被刪除結點所在的子樹不符合原紅黑樹的特性1、2、4或5(非刪除結點不受影響)。因此,只需要對子樹進行顏色調整,就能使紅黑樹性質保持不變。
偽碼中Transplant函數的實現
如何刪除的原理已經講明白了,那么我們看,兩個結點是如何替換(也就是發生刪除操作的)。

在偽碼中,結點u為被替換結點,你可以理解為,被刪除結點,而v是用來替換被刪除結點的結點(通常為u的子節點或者u的右子樹結點的最小節點)。
下面是我實現的transplant函數:
1 template<typename T>
2 void RedBlackTree<T>::Transplant(RedBlackNode<T>* &u, RedBlackNode<T>* &v){ 3 /* 4 *a function to achieve node u is replaced by v. 5 *@Parameter: 6 *u: a node which is replaced by v. 7 *v: a node wants to replace u. 8 */ 9 if(Parent(u) == NIL){//待刪除結點為根結點. 10 root = v; 11 } 12 else if(u==Parent(u)->lchild){ 13 Parent(u)->lchild = v; 14 } 15 else{ 16 Parent(u)->rchild = v; 17 } 18 }
紅黑樹刪除操作的具體實現
下面給出刪除操作的偽代碼(源自《算法導論》)。

在上述代碼中,結點z為刪除結點,y為指向結點z的指針。我們知道,BST的刪除操作是很容易實現的,對於紅黑樹來說,關鍵在於,刪除操作以后,什么情況下,會破壞紅黑樹的紅黑性質。
由於y的顏色有可能發生改變(因為根據代碼,y始終指向樹結構中被刪除結點的位置),用變量y_original_color存儲了發生改變前的y位置的顏色。第2行和第10行在給y賦值之后,立即設置該變量。當z有兩個子結點時,則y!=z且結點y移至紅黑樹中結點z的原始位置;第20行給y賦予和z一樣的顏色。然后保存y的原始顏色,以在刪除操作結束時,測試它;如果它是黑色的,那么刪除或移動y會引起紅黑性質的破壞,為什么會是黑色引起紅黑性質的破壞呢?
- 對於情況1,即不存在子結點的被刪除結點來說,什么情況下刪除該結點以后會改變原有紅黑樹的性質呢?很顯然,被刪除結點是黑色的時候,刪除它會違背紅黑樹的性質5,而被刪除結點為紅色的時候,刪除它並不會影響紅黑樹的性質,直接修改即可(如下所示),在這里,就產生了刪除結點后,后續修改顏色的第一種情況。

如上圖所示,如果刪除結點5,對於左側的樹,如果刪除結點y,則結點7不會違反任何紅黑樹的性質,因為結點y的子結點為必定為黑色(由於紅黑樹的性質),因此,y為紅色不會引起紅黑樹性質的改變;對於右側的樹,如果刪除結點y,則如果結點y的子結點為NIL(黑色),不會引起結點7與結點y子結點之間都為紅色,從而不違反了紅黑樹的性質。
- 對於情況2,即存在左結點或者右結點,由於紅黑樹結點存在color屬性,因此,常見的做法是將用來替換刪除結點的結點的顏色改成與刪除結點顏色一致,這樣,只需要對修改過指針結構后的子樹進行修改其顏色,即可完成紅黑樹性質的保持。那么,由於顏色的存在,又會有那些情況的出現呢?我們知道,一個結點無非是紅色或者黑色,且根據性質,紅結點的子結點顏色必為黑色,那么,以被刪除結點為左結點為例,有下面兩種情況:

從左邊的紅黑樹來看,如果待刪除結點顏色為黑色,當對該結點進行操作時,則由於,其子結點為紅色,與y結點的父結點同為紅色,因此,會違背紅黑樹的性質,而如果是右邊的情況,則不會,因為刪除結點y以后,由於結點4依舊為黑色,不會破壞紅黑樹的性質。對於這種情況下,左子樹結點不存在而右子樹結點存在的情況,也是同樣的道理,讀者可以自己畫圖思考一下。
- 對於情況3,即被刪除結點同時存在左右子結點,如下圖所示:

從上圖來看,如果刪除結點5,會與情況2一樣而違反性質,而對於右邊的樹,則不會,因為,我們刪除的方式,是將z結點也就是結點5的左子樹,連接到結點5右子樹的值最小的結點上。然后用這個最小結點來替換原來結點z(也就是圖上結點5的位置)。這樣做的好處是,仍可以保證刪除后的樹仍滿足BST樹的性質,我們只需要對被刪除結點的子樹進行修改顏色性質就可以了,而且,不論最小結點的顏色如何,都不會導致出現兩個紅結點的情況。這一點可能很多人會存在疑惑,我們來分析一下:
對於左右子樹都存在的的刪除結點來說,此時,y從指向z轉向了指向刪除結點z的右子樹種最小的一個結點(右子樹的最左結點),這樣的指向,無非兩種情況,一種是右子樹的最左結點就是被刪除結點z的右孩子,即其右孩子的左孩子為NIL,這樣,以上右圖為例,y指向了結點6,然6后,用y_original_color來保存結點6的顏色,用x指向6的右孩子,由於在這里,y的父親結點依舊為刪除結點z,因此,設置好結點屬性x.p = y,然后,執行替換操作(Transplant)來實現刪除的目的,可以看到,transplant操作是對刪除結點z和替換結點y進行操作的,對於這種情況來說,是將z與其右孩子進行替換,根據偽碼,結點7的左孩子指向了結點6。然后結點6也就是現在的結點y的左孩子指向了結點z的左孩子,這樣就完成了然后將結點y的顏色,改成結點z的顏色,為什么這么做呢,是為了保持與原來紅黑樹相同的特性,因為我們知道,在刪除結點5之前,結點5左右兩棵子樹的一條路徑上的黑色結點數目是相同的,但是,由於結點6的上位(替換了其父結點),而父結點的左孩子直接成為其左子樹,這就導致了左右兩子樹的不平衡,調整為與z結點相同的顏色以后,可以使得對紅黑樹修改操作僅局限於結點y這棵子樹中進行。
另一種情況則是右子樹的最左結點不是被刪除結點z的右孩子,即其右孩子的左孩子非空,那么,其余操作不變,比較巧妙的是,這里運用了最左結點的左孩子為NIL的特性,將最左結點的右子樹接到了其父節點的左邊(這就保證了BST樹的有序性),且這樣的做法,使得該結點與樹在結構上斷開,無法通過樹的根結點訪問到,因此,后續再執行一次刪除結點z與結點y的替換操作,就可以完成這樣的刪除操作。這種情況下,會產生y為紅色或者黑色結點的問題,同樣,也只會有黑色會違背紅黑樹的性質。
紅黑樹刪除操作后的修改結點顏色操作
通過上面的分析,我們很容易得到這樣的結論,那就是,只有當y為黑色結點的時候,才會發生違背紅黑樹性質的情況,因此,需要對這樣的情況進行修改,來保持紅黑樹的性質。下面給出《算法導論》中,關於該操作的偽代碼:

在分析源碼以前,我們首先來分析,執行刪除操作以后,會出現哪些違背紅黑樹性質的情況。在這個操作中,結合刪除操作的代碼,我們可以發現,x始終指向具有雙重黑色的非根結點。那么,就會有4種情況,這四種情況與插入操作中的是類似的,請讀者結合上述刪除操作,自行分析。
下面附上紅黑樹刪除操作的代碼。
1 template<typename T> 2 void RedBlackTree<T>::Delete(RedBlackNode<T>* &t){ 3 /* 4 *function to delete node t in redblacktree. 5 *@Parameter: 6 *t: a node need to be deleted. 7 */ 8 RedBlackNode<T> *y; 9 RedBlackNode<T> *p; 10 y = t; 11 string y_original_color = y->color; 12 if(t->lchild==NIL){ 13 p = t->rchild; 14 Transplant(t, t->rchild); 15 } 16 else if(t->rchild==NIL){ 17 p = t->lchild; 18 Transplant(t, t->lchild); 19 } 20 else{ 21 y = TreeMinimum(t->rchild); 22 y_original_color = y->color; 23 p = y->rchild; 24 if(Parent(y)!=t){ 25 Transplant(y, y->rchild); 26 y->rchild = t->rchild; 27 } 28 Transplant(t, y); 29 y->lchild = t->lchild; 30 y->color = t->color; 31 } 32 33 if(y_original_color=="black"){ 34 RBDeleteFixup(p); 35 } 36 delete t; 37 t = NULL; 38 }
紅黑樹刪除后,修改顏色的操作代碼。
template<typename T> RedBlackNode<T>* RedBlackTree<T>::TreeMinimum(RedBlackNode<T>* t){ RedBlackNode<T> *p; while(t!=NIL){ p = t; t = t->lchild; } return p; } template<typename T> void RedBlackTree<T>::RBDeleteFixup(RedBlackNode<T>* &t){ RedBlackNode<T> *p; RedBlackNode<T> *f; while(t!=NIL&&t->color=="black"){ if(t==Parent(t)->lchild){ p = Parent(t)->rchild; if(p->color=="red"){ p->color = "black"; Parent(t)->color = "red"; f = Parent(t); LeftRotation(f); p = Parent(t)->rchild; } if(p->lchild->color=="black"&&p->rchild->color=="black"){ p->color = "red"; t = Parent(t); } else if(p->rchild->color=="black"){ p->lchild->color = "black"; p->color = "red"; RightRotation(p); p = Parent(t)->rchild; } else{ p->color = Parent(t)->color; Parent(t)->color = "black"; p->rchild->color = "black"; f = Parent(t); LeftRotation(f); t = root; } } else{ p = Parent(t)->lchild; if(p->color=="red"){ p->color = "black"; Parent(t)->color = "red"; f = Parent(t); RightRotation(f); p = Parent(t)->lchild; } if(p->rchild->color=="black"&&p->lchild->color=="black"){ p->color = "red"; t = Parent(t); } else if(p->lchild->color=="black"){ p->rchild->color = "black"; p->color = "red"; LeftRotation(p); p = Parent(t)->lchild; } else{ p->color = Parent(t)->color; Parent(t)->color = "black"; p->lchild->color = "black"; f = Parent(t); RightRotation(f); t = root; } } } t->color = "black"; }
