紅黑樹(英語:Red–black tree)是一種自平衡二叉查找樹,是在計算機科學中用到的一種數據結構,典型的用途是實現關聯數組。它是在1972年由魯道夫·貝爾發明的,他稱之爲"對稱二叉B樹",它現代的名字是在Leo J. Guibas和Robert Sedgewick於1978年寫的一篇論文中得到的。它是複雜的,但它的操做有着良好的最壞狀況運行時間,而且在實踐中是高效的:它能夠在O(log n)時間內作查找,插入和刪除,這裏的n是樹中元素的數目。 php
紅黑樹和AVL樹同樣都對插入時間、刪除時間和查找時間提供了最好可能的最壞狀況擔保。這不僅是使它們在時間敏感的應用如實時應用(real time application)中有價值,並且使它們有在提供最壞狀況擔保的其餘數據結構中做爲建造板塊的價值;例如,在計算幾何中使用的不少數據結構均可以基於紅黑樹。 html
紅黑樹在函數式編程中也特別有用,在這裏它們是最經常使用的持久數據結構(persistent data structure)之一,它們用來構造關聯數組和集合,每次插入、刪除以後它們能保持爲之前的版本。除了O(log n)的時間以外,紅黑樹的持久版本對每次插入或刪除須要O(log n)的空間。 node
紅黑樹是2-3-4樹的一種等同。換句話說,對於每一個2-3-4樹,都存在至少一個數據元素是一樣次序的紅黑樹。在2-3-4樹上的插入和刪除操做也等同於在紅黑樹中顏色翻轉和旋轉。這使得2-3-4樹成爲理解紅黑樹背後的邏輯的重要工具,這也是不少介紹算法的教科書在紅黑樹以前介紹2-3-4樹的緣由,儘管2-3-4樹在實踐中不常用。 web
紅黑樹是每一個節點都帶有顏色屬性的二叉查找樹,顏色爲紅色或黑色。在二叉查找樹強制通常要求之外,對於任何有效的紅黑樹咱們增長了以下的額外要求: 算法
下面是一個具體的紅黑樹的圖例: 編程
這些約束確保了紅黑樹的關鍵特性:從根到葉子的最長的可能路徑很少於最短的可能路徑的兩倍長。結果是這個樹大體上是平衡的。由於操做好比插入、刪除和查找某個值的最壞狀況時間都要求與樹的高度成比例,這個在高度上的理論上限容許紅黑樹在最壞狀況下都是高效的,而不一樣於普通的二叉查找樹。 數組
要知道爲何這些性質確保了這個結果,注意到性質4致使了路徑不能有兩個毗連的紅色節點就足夠了。最短的可能路徑都是黑色節點,最長的可能路徑有交替的紅色和黑色節點。由於根據性質5全部最長的路徑都有相同數目的黑色節點,這就代表了沒有路徑能多於任何其餘路徑的兩倍長。 數據結構
在不少樹數據結構的表示中,一個節點有可能只有一個子節點,而葉子節點包含數據。用這種範例表示紅黑樹是可能的,可是這會改變一些性質並使算法複雜。爲此,本文中咱們使用"nil葉子"或"空(null)葉子",如上圖所示,它不包含數據而只充當樹在此結束的指示。這些節點在繪圖中常常被省略,致使了這些樹好像同上述原則相矛盾,而實際上不是這樣。與此有關的結論是全部節點都有兩個子節點,儘管其中的一個或兩個多是空葉子。 app
由於每個紅黑樹也是一個特化的二叉查找樹,所以紅黑樹上的只讀操做與普通二叉查找樹上的只讀操做相同。然而,在紅黑樹上進行插入操做和刪除操做會致使再也不符合紅黑樹的性質。恢復紅黑樹的性質須要少許(O(log n))的顏色變動(實際是很是快速的)和不超過三次樹旋轉(對於插入操做是兩次)。雖然插入和刪除很複雜,但操做時間仍能夠保持爲O(logn)次。 ide
咱們首先以二叉查找樹的方法增長節點並標記它爲紅色。(若是設爲黑色,就會致使根到葉子的路徑上有一條路上,多一個額外的黑節點,這個是很難調整的。可是設爲紅色節點後,可能會致使出現兩個連續紅色節點的衝突,那麼能夠經過顏色調換(color flips)和樹旋轉來調整。)下面要進行什麼操做取決於其餘臨近節點的顏色。同人類的家族樹中同樣,咱們將使用術語叔父節點來指一個節點的父節點的兄弟節點。注意:
在下面的示意圖中,將要插入的節點標爲N,N的父節點標爲P,N的祖父節點標爲G,N的叔父節點標爲U。在圖中展現的任何顏色要麼是由它所處情形這些所做的假定,要麼是假定所暗含(imply)的。
對於每一種情形,咱們將使用C示例代碼來展現。經過下列函數,能夠找到一個節點的叔父和祖父節點:
node* grandparent(node *n){
return n->parent->parent;
}
node* uncle(node *n){
if(n->parent == grandparent(n)->left)
return grandparent (n)->right;
else return grandparent (n)->left;
}
情形1:新節點N位於樹的根上,沒有父節點。在這種情形下,咱們把它重繪爲黑色以知足性質2。由於它在每一個路徑上對黑節點數目增長一,性質5符合。
void insert_case1(node *n){
if(n->parent == NULL)
n->color = BLACK;
else insert_case2 (n);
}
情形2:新節點的父節點P是黑色,因此性質4沒有失效(新節點是紅色的)。在這種情形下,樹還是有效的。性質5也未受到威脅,儘管新節點N有兩個黑色葉子子節點;但因爲新節點N是紅色,經過它的每一個子節點的路徑就都有同經過它所取代的黑色的葉子的路徑一樣數目的黑色節點,因此依然知足這個性質。
void insert_case2(node *n){
if(n->parent->color == BLACK)
return; /* 樹仍舊有效*/
else insert_case3 (n);
}
注意:在下列情形下咱們假定新節點的父節點爲紅色,因此它有祖父節點;由於若是父節點是根節點,那父節點就應當是黑色。因此新節點總有一個叔父節點,儘管在情形4和5下它多是葉子節點。
情形3:若是父節點P和叔父節點U兩者都是紅色,(此時新插入節點N作爲P的左子節點或右子節點都屬於情形3,這裏右圖僅顯示N作爲P左子的情形)則咱們能夠將它們兩個重繪爲黑色並重繪祖父節點G爲紅色(用來保持性質4)。如今咱們的新節點N有了一個黑色的父節點P。由於經過父節點P或叔父節點U的任何路徑都一定經過祖父節點G,在這些路徑上的黑節點數目沒有改變。可是,紅色的祖父節點G多是根節點,這就違反了性質2,也有可能祖父節點G的父節點是紅色的,這就違反了性質4。爲了解決這個問題,咱們在祖父節點G上遞歸地進行情形1的整個過程。(把G當成是新加入的節點進行各類情形的檢查) |
void insert_case3(node *n){
if(uncle(n) != NULL && uncle (n)->color == RED) {
n->parent->color = BLACK;
uncle (n)->color = BLACK;
grandparent (n)->color = RED;
insert_case1(grandparent(n));
}
else insert_case4 (n);
}
注意:在餘下的情形下,咱們假定父節點P是其父親G的左子節點。若是它是右子節點,情形4和情形5中的左和右應當對調。
情形4:父節點P是紅色而叔父節點U是黑色或缺乏,而且新節點N是其父節點P的右子節點而父節點P又是其父節點的左子節點。在這種情形下,咱們進行一次左旋轉調換新節點和其父節點的角色;接着,咱們按情形5處理之前的父節點P以解決仍然失效的性質4。注意這個改變會致使某些路徑經過它們之前不經過的新節點N(好比圖中1號葉子節點)或不經過節點P(好比圖中3號葉子節點),但因爲這兩個節點都是紅色的,因此性質5仍有效。 |
void insert_case4(node *n){
if(n == n->parent->right && n->parent == grandparent(n)->left) {
rotate_left(n->parent);
n = n->left;
}
else if(n == n->parent->left && n->parent == grandparent(n)->right) {
rotate_right(n->parent);
n = n->right;
}
insert_case5 (n);
}
情形5:父節點P是紅色而叔父節點U是黑色或缺乏,新節點N是其父節點的左子節點,而父節點P又是其父節點G的左子節點。在這種情形下,咱們進行鍼對祖父節點G的一次右旋轉;在旋轉產生的樹中,之前的父節點P如今是新節點N和之前的祖父節點G的父節點。咱們知道之前的祖父節點G是黑色,不然父節點P就不多是紅色(若是P和G都是紅色就違反了性質4,因此G必須是黑色)。咱們切換之前的父節點P和祖父節點G的顏色,結果的樹知足性質4。性質5也仍然保持知足,由於經過這三個節點中任何一個的全部路徑之前都經過祖父節點G,如今它們都經過之前的父節點P。在各自的情形下,這都是三個節點中惟一的黑色節點。 |
void insert_case5(node *n){
n->parent->color = BLACK;
grandparent (n)->color = RED;
if(n == n->parent->left && n->parent == grandparent(n)->left) {
rotate_right(grandparent(n));
}
else {
/* Here, n == n->parent->right && n->parent == grandparent (n)->right */
rotate_left(grandparent(n));
}
}
若是須要刪除的節點有兩個兒子,那麼問題能夠被轉化成刪除另外一個只有一個兒子的節點的問題(爲了表述方便,這裏所指的兒子,爲非葉子節點的兒子)。對於二叉查找樹,在刪除帶有兩個非葉子兒子的節點的時候,咱們找到要麼在它的左子樹中的最大元素、要麼在它的右子樹中的最小元素,並把它的值轉移到要刪除的節點中(如在這裏所展現的那樣)。咱們接着刪除咱們從中複製出值的那個節點,它一定有少於兩個非葉子的兒子。由於只是複製了一個值,不違反任何性質,這就把問題簡化爲如何刪除最多有一個兒子的節點的問題。它不關心這個節點是最初要刪除的節點仍是咱們從中複製出值的那個節點。
在本文餘下的部分中,咱們只須要討論刪除只有一個兒子的節點(若是它兩個兒子都爲空,即均爲葉子,咱們任意將其中一個看做它的兒子)。若是咱們刪除一個紅色節點(此時該節點的兒子將都爲葉子節點),它的父親和兒子必定是黑色的。因此咱們能夠簡單的用它的黑色兒子替換它,並不會破壞性質3和性質4。經過被刪除節點的全部路徑只是少了一個紅色節點,這樣能夠繼續保證性質5。另外一種簡單狀況是在被刪除節點是黑色而它的兒子是紅色的時候。若是隻是去除這個黑色節點,用它的紅色兒子頂替上來的話,會破壞性質5,可是若是咱們重繪它的兒子爲黑色,則曾經經過它的全部路徑將經過它的黑色兒子,這樣能夠繼續保持性質5。
須要進一步討論的是在要刪除的節點和它的兒子兩者都是黑色的時候,這是一種複雜的狀況。咱們首先把要刪除的節點替換爲它的兒子。出於方便,稱呼這個兒子爲N(在新的位置上),稱呼它的兄弟(它父親的另外一個兒子)爲S。在下面的示意圖中,咱們仍是使用P稱呼N的父親,SL稱呼S的左兒子,SR稱呼S的右兒子。咱們將使用下述函數找到兄弟節點:
struct node * sibling(struct node *n) {
if(n == n->parent->left)
return n->parent->right;
else return n->parent->left;
}
咱們可使用下列代碼進行上述的概要步驟,這裏的函數replace_node替換child到n在樹中的位置。出於方便,在本章節中的代碼將假定空葉子被用不是NULL的實際節點對象來表示(在插入章節中的代碼能夠同任何一種表示一塊兒工做)。
void delete_one_child(struct node *n) {
/* * Precondition: n has at most one non-null child. */
struct node *child = is_leaf(n->right)? n->left : n->right; replace_node(n, child);
if(n->color == BLACK){ if(child->color == RED)
child->color = BLACK;
else delete_case1 (child);
}
free (n);
}
若是N和它初始的父親是黑色,則刪除它的父親致使經過N的路徑都比不經過它的路徑少了一個黑色節點。由於這違反了性質5,樹須要被從新平衡。有幾種情形須要考慮:
情形1: N是新的根。在這種情形下,咱們就作完了。咱們從全部路徑去除了一個黑色節點,而新根是黑色的,因此性質都保持着。
void delete_case1(struct node *n) {
if(n->parent != NULL) delete_case2 (n);
}
注意:在情形二、5和6下,咱們假定N是它父親的左兒子。若是它是右兒子,則在這些情形下的左和右應當對調。
情形2: S是紅色。在這種情形下咱們在N的父親上作左旋轉,把紅色兄弟轉換成N的祖父,咱們接着對調N的父親和祖父的顏色。完成這兩個操做後,儘管全部路徑上黑色節點的數目沒有改變,但如今N有了一個黑色的兄弟和一個紅色的父親(它的新兄弟是黑色由於它是紅色S的一個兒子),因此咱們能夠接下去按情形4、情形5或情形6來處理。 |
void delete_case2(struct node *n) {
struct node *s = sibling (n);
if(s->color == RED){
n->parent->color = RED;
s->color = BLACK;
if(n == n->parent->left)
rotate_left(n->parent);
else rotate_right(n->parent);
}
delete_case3 (n);
}
情形3: N的父親、S和S的兒子都是黑色的。在這種情形下,咱們簡單的重繪S爲紅色。結果是經過S的全部路徑,它們就是之前不經過N的那些路徑,都少了一個黑色節點。由於刪除N的初始的父親使經過N的全部路徑少了一個黑色節點,這使事情都平衡了起來。可是,經過P的全部路徑如今比不經過P的路徑少了一個黑色節點,因此仍然違反性質5。要修正這個問題,咱們要從情形1開始,在P上作從新平衡處理。 |
void delete_case3(struct node *n) {
struct node *s = sibling (n);
if((n->parent->color == BLACK)&& (s->color == BLACK)&& (s->left->color == BLACK)&& (s->right->color == BLACK)) {
s->color = RED;
delete_case1(n->parent);
}
else
delete_case4 (n);
}
情形4: S和S的兒子都是黑色,可是N的父親是紅色。在這種情形下,咱們簡單的交換N的兄弟和父親的顏色。這不影響不經過N的路徑的黑色節點的數目,可是它在經過N的路徑上對黑色節點數目增長了一,添補了在這些路徑上刪除的黑色節點。 |
void delete_case4(struct node *n) {
struct node *s = sibling (n);
if((n->parent->color == RED)&& (s->color == BLACK)&& (s->left->color == BLACK)&& (s->right->color == BLACK)) {
s->color = RED;
n->parent->color = BLACK;
} else
delete_case5 (n);
}
情形5: S是黑色,S的左兒子是紅色,S的右兒子是黑色,而N是它父親的左兒子。在這種情形下咱們在S上作右旋轉,這樣S的左兒子成爲S的父親和N的新兄弟。咱們接着交換S和它的新父親的顏色。全部路徑仍有一樣數目的黑色節點,可是如今N有了一個黑色兄弟,他的右兒子是紅色的,因此咱們進入了情形6。N和它的父親都不受這個變換的影響。 |
void delete_case5(struct node *n) {
struct node *s = sibling (n);
if(s->color == BLACK){
/* this if statement is trivial, due to Case 2(even though Case two changed the sibling to a sibling's child, the sibling's child can't be red, since no red parent can have a red child). */
// the following statements just force the red to be on the left of the left of the parent,
// or right of the right, so case six will rotate correctly.
if((n == n->parent->left)&& (s->right->color == BLACK)&& (s->left->color == RED)) {
// this last test is trivial too due to cases 2-4.
s->color = RED;
s->left->color = BLACK;
rotate_right (s);
}
else if((n == n->parent->right)&& (s->left->color == BLACK)&& (s->right->color == RED)) {
// this last test is trivial too due to cases 2-4.
s->color = RED;
s->right->color = BLACK;
rotate_left (s);
}
}
delete_case6 (n);
}
情形6: S是黑色,S的右兒子是紅色,而N是它父親的左兒子。在這種情形下咱們在N的父親上作左旋轉,這樣S成爲N的父親(P)和S的右兒子的父親。咱們接着交換N的父親和S的顏色,並使S的右兒子爲黑色。子樹在它的根上的還是一樣的顏色,因此性質3沒有被違反。可是,N如今增長了一個黑色祖先:要麼N的父親變成黑色,要麼它是黑色而S被增長爲一個黑色祖父。因此,經過N的路徑都增長了一個黑色節點。 此時,若是一個路徑不經過N,則有兩種可能性:
在任何狀況下,在這些路徑上的黑色節點數目都沒有改變。因此咱們恢復了性質4。在示意圖中的白色節點能夠是紅色或黑色,可是在變換先後都必須指定相同的顏色。 |
void delete_case6(struct node *n) {
struct node *s = sibling (n);
s->color = n->parent->color;
n->parent->color = BLACK;
if(n == n->parent->left){
s->right->color = BLACK;
rotate_left(n->parent);
}
else {
s->left->color = BLACK;
rotate_right(n->parent);
}
}
一樣的,函數調用都使用了尾部遞歸,因此算法是原地算法。此外,在旋轉以後再也不作遞歸調用,因此進行了恆定數目(最多3次)的旋轉。
#define BLACK 1 #define RED 0 using namespace std; class bst { private: struct Node { int value; bool color; Node *leftTree, *rightTree, *parent; Node() { color = RED; leftTree = NULL; rightTree = NULL; parent = NULL; value = 0; } Node* grandparent() { if(parent == NULL){ return NULL; } return parent->parent; } Node* uncle() { if(grandparent() == NULL) { return NULL; } if(parent == grandparent()->rightTree) return grandparent()->leftTree; else return grandparent()->rightTree; } Node* sibling() { if(parent->leftTree == this) return parent->rightTree; else return parent->leftTree; } }; void rotate_right(Node *p){ Node *gp = p->grandparent(); Node *fa = p->parent; Node *y = p->rightTree; fa->leftTree = y; if(y != NIL) y->parent = fa; p->rightTree = fa; fa->parent = p; if(root == fa) root = p; p->parent = gp; if(gp != NULL){ if(gp->leftTree == fa) gp->leftTree = p; else gp->rightTree = p; } } void rotate_left(Node *p){ if(p->parent == NULL){ root = p; return; } Node *gp = p->grandparent(); Node *fa = p->parent; Node *y = p->leftTree; fa->rightTree = y; if(y != NIL) y->parent = fa; p->leftTree = fa; fa->parent = p; if(root == fa) root = p; p->parent = gp; if(gp != NULL){ if(gp->leftTree == fa) gp->leftTree = p; else gp->rightTree = p; } } void inorder(Node *p){ if(p == NIL) return; if(p->leftTree) inorder(p->leftTree); cout << p->value << " "; if(p->rightTree) inorder(p->rightTree); } string outputColor (bool color) { return color ? "BLACK" : "RED"; } Node* getSmallestChild(Node *p){ if(p->leftTree == NIL) return p; return getSmallestChild(p->leftTree); } bool delete_child(Node *p, int data){ if(p->value > data){ if(p->leftTree == NIL){ return false; } return delete_child(p->leftTree, data); } else if(p->value < data){ if(p->rightTree == NIL){ return false; } return delete_child(p->rightTree, data); } else if(p->value == data){ if(p->rightTree == NIL){ delete_one_child (p); return true; } Node *smallest = getSmallestChild(p->rightTree); swap(p->value, smallest->value); delete_one_child (smallest); return true; } } void delete_one_child(Node *p){ Node *child = p->leftTree == NIL ? p->rightTree : p->leftTree; if(p->parent == NULL && p->leftTree == NIL && p->rightTree == NIL){ p = NULL; root = p; return; } if(p->parent == NULL){ delete p; child->parent = NULL; root = child; root->color = BLACK; return; } if(p->parent->leftTree == p){ p->parent->leftTree = child; } else { p->parent->rightTree = child; } child->parent = p->parent; if(p->color == BLACK){ if(child->color == RED){ child->color = BLACK; } else delete_case (child); } delete p; } void delete_case(Node *p){ if(p->parent == NULL){ p->color = BLACK; return; } if(p->sibling()->color == RED) { p->parent->color = RED; p->sibling()->color = BLACK; if(p == p->parent->leftTree) rotate_left(p->sibling()); else rotate_right(p->sibling()); } if(p->parent->color == BLACK && p->sibling()->color == BLACK && p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK) { p->sibling()->color = RED; delete_case(p->parent); } else if(p->parent->color == RED && p->sibling()->color == BLACK && p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK) { p->sibling()->color = RED; p->parent->color = BLACK; } else { if(p->sibling()->color == BLACK) { if(p == p->parent->leftTree && p->sibling()->leftTree->color == RED && p->sibling()->rightTree->color == BLACK) { p->sibling()->color = RED; p->sibling()->leftTree->color = BLACK; rotate_right(p->sibling()->leftTree); } else if(p == p->parent->rightTree && p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == RED) { p->sibling()->color = RED; p->sibling()->rightTree->color = BLACK; rotate_left(p->sibling()->rightTree); } } p->sibling()->color = p->parent->color; p->parent->color = BLACK; if(p == p->parent->leftTree){ p->sibling()->rightTree->color = BLACK; rotate_left(p->sibling()); } else { p->sibling()->leftTree->color = BLACK; rotate_right(p->sibling()); } } } void insert(Node *p, int data){ if(p->value >= data){ if(p->leftTree != NIL) insert(p->leftTree, data); else { Node *tmp = new Node(); tmp->value = data; tmp->leftTree = tmp->rightTree = NIL; tmp->parent = p; p->leftTree = tmp; insert_case (tmp); } } else { if(p->rightTree != NIL) insert(p->rightTree, data); else { Node *tmp = new Node(); tmp->value = data; tmp->leftTree = tmp->rightTree = NIL; tmp->parent = p; p->rightTree = tmp; insert_case (tmp); } } } void insert_case(Node *p){ if(p->parent == NULL){ root = p; p->color = BLACK; return; } if(p->parent->color == RED){ if(p->uncle()->color == RED) { p->parent->color = p->uncle()->color = BLACK; p->grandparent()->color = RED; insert_case(p->grandparent()); } else { if(p->parent->rightTree == p && p->grandparent()->leftTree == p->parent) { rotate_left (p); rotate_right (p); p->color = BLACK; p->leftTree->color = p->rightTree->color = RED; } else if(p->parent->leftTree == p && p->grandparent()->rightTree == p->parent) { rotate_right (p); rotate_left (p); p->color = BLACK; p->leftTree->color = p->rightTree->color = RED; } else if(p->parent->leftTree == p && p->grandparent()->leftTree == p->parent) { p->parent->color = BLACK; p->grandparent()->color = RED; rotate_right(p->parent); } else if(p->parent->rightTree == p && p->grandparent()->rightTree == p->parent) { p->parent->color = BLACK; p->grandparent()->color = RED; rotate_left(p->parent); } } } } void DeleteTree(Node *p){ if(!p || p == NIL){ return; } DeleteTree(p->leftTree); DeleteTree(p->rightTree); delete p; } public: bst() { NIL = new Node(); NIL->color = BLACK; root = NULL; } ~bst() { if (root) DeleteTree (root); delete NIL; } void inorder() { if(root == NULL) return; inorder (root); cout << endl; } void insert (int x) { if(root == NULL){ root = new Node(); root->color = BLACK; root->leftTree = root->rightTree = NIL; root->value = x; } else { insert(root, x); } } bool delete_value (int data) { return delete_child(root, data); } private: Node *root, *NIL; };
包含n個內部節點的紅黑樹的高度是O(log(n))。
定義:
引理:以節點v爲根的子樹有至少個內部節點。
引理的證實(經過概括高度):
基礎:h(v) = 0
若是v的高度是零則它一定是nil,所以bh(v) = 0。因此:
概括假設:h(v) = k的v有個內部節點暗示了h(
) = k+1的
有
個內部節點。
由於有h(
)> 0因此它是個內部節點。一樣的它有黑色高度要麼是bh(
)要麼是bh(
)-1(依據
是紅色仍是黑色)的兩個兒子。經過概括假設每一個兒子都有至少
個內部接點,因此
有:
個內部節點。
使用這個引理咱們如今能夠展現出樹的高度是對數性的。由於在從根到葉子的任何路徑上至少有一半的節點是黑色(根據紅黑樹性質4),根的黑色高度至少是h(root)/2。經過引理咱們獲得:
所以根的高度是O(log(n))。
![]() |
維基共享資源中相關的多媒體資源:紅黑樹 |
|