紅黑樹

紅黑樹（Red–black tree）是一種自平衡二叉查找樹，是在計算機科學中用到的一種數據結構，典型用途是實現關聯數組。它在1972年由魯道夫·貝爾發明，被稱為「對稱二叉B樹」，它現代的名字源於利奧尼達斯·J·吉巴斯和羅伯特·塞奇威克於1978年寫的一篇論文。紅黑樹的結構複雜，但它的操作有著良好的最壞情況運行時間，並且在實踐中高效：它可以在時間內完成查找、插入和刪除，這裡的是樹中元素的數目。

**歷史**
1972年，魯道夫·拜爾發明瞭一種資料結構，那是一個B樹的四階特殊情況。此結構會維持所有從根到樹葉的路徑都具有相同數量的節點，創造了一個完美的平衡樹。然而，他們並不是二元搜尋樹。拜爾在他的論文中稱呼他們為「對稱二元B樹」（Symmetric binary B-tree），後來他們以2-3-4樹或是2-4樹的形式開始流行。
在一篇1972年的論文〈A Dichromatic Framework for Balanced Trees〉中，利奧尼達斯·J·吉巴斯和羅伯特·塞奇威克從對稱二元B樹+中推導出了紅黑樹。之所以選擇「紅色」是因為這是作者在帕羅奧多研究中心公司（Xerox PARC）工作時用彩色雷射列印機可以產生的最好看的顏色。另一種說法來自吉巴斯，是因為他們當時能用來繪製樹的筆有紅色和黑色。

**用途和好處**
紅黑樹和AVL樹一樣都對插入時間、刪除時間和查找時間提供了最好可能的最壞情況擔保。這不只是使它們在時間敏感的應用，如即時應用中有價值，而且使它們有在提供最壞情況擔保的其他數據結構中作為基礎模板的價值；例如，在計算幾何中使用的很多數據結構都可以基於紅黑樹實現。
紅黑樹在函數式編程中也特別有用，在這裡它們是最常用的持久數據結構之一，它們用來構造關聯數組和集合，每次插入、刪除之後它們能保持為以前的版本。除了的時間之外，紅黑樹的持久版本對每次插入或刪除需要的空間。
紅黑樹是2-3-4樹的一種等同。換句話說，對於每個2-3-4樹，都存在至少一個數據元素是同樣次序的紅黑樹。在2-3-4樹上的插入和刪除操作也等同於在紅黑樹中顏色翻轉和旋轉。這使得2-3-4樹成為理解紅黑樹背後的邏輯的重要工具，這也是很多介紹算法的教科書在紅黑樹之前介紹2-3-4樹的原因，儘管2-3-4樹在實踐中不經常使用。
紅黑樹相對於AVL樹來說，犧牲了部分平衡性以換取插入和刪除操作時少量的旋轉操作，整體來說性能要優於AVL樹。

**性質**
紅黑樹是每個節點都帶有顏色屬性的二叉查找樹，顏色為紅色或黑色。在二叉查找樹強制一般要求以外，對於任何有效的紅黑樹我們增加了如下的額外要求：
# 節點是紅色或黑色。
# 根是黑色。
# 所有葉子都是黑色（葉子是NIL節點）。
# 每個紅色節點必須有兩個黑色的子節點。（或者說從每個葉子到根的所有路徑上不能有兩個連續的紅色節點。）（或者說不存在兩個相鄰的紅色節點，相鄰指兩個節點是父子關係。）（或者說紅色節點的父節點和子節點均是黑色的。）
# 從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點。
下面是一個具體的紅黑樹的圖例：
An example of a red-black tree
這些約束確保了紅黑樹的關鍵特性：從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長。結果是這個樹大致上是平衡的。因為操作比如插入、刪除和查找某個值的最壞情況時間都要求與樹的高度成比例，這個在高度上的理論上限允許紅黑樹在最壞情況下都是高效的，而不同於普通的二叉查找樹。
要知道為什麼這些性質確保了這個結果，注意到性質4導致了路徑不能有兩個毗連的紅色節點就足夠了。最短的可能路徑都是黑色節點，最長的可能路徑有交替的紅色和黑色節點。因為根據性質5所有最長的路徑都有相同數目的黑色節點，這就表明了沒有路徑能多於任何其他路徑的兩倍長。
在很多樹數據結構的表示中，一個節點有可能只有一個子節點，而葉子節點包含數據。用這種範例表示紅黑樹是可能的，但是這會改變一些性質並使算法複雜。為此，本文中我們使用「nil葉子」，如上圖所示，它不包含數據而只充當樹在此結束的指示。這些節點在繪圖中經常被省略，導致了這些樹好像同上述原則相矛盾，而實際上不是這樣。與此有關的結論是所有節點都有兩個子節點，儘管其中的一個或兩個可能是空葉子。

**操作**
因為每一個紅黑樹也是一個特化的二叉查找樹，因此紅黑樹上的只讀操作與普通二叉查找樹上的只讀操作相同。然而，在紅黑樹上進行插入操作和刪除操作會導致不再符合紅黑樹的性質。恢復紅黑樹的性質需要少量（）的顏色變更（實際是非常快速的）和不超過三次樹旋轉（對於插入操作是兩次）。雖然插入和刪除很複雜，但操作時間仍可以保持為次。

**插入**
我們首先以二叉查找樹的方法增加節點並標記它為紅色。（如果設為黑色，就會導致根到葉子的路徑上有一條路上，多一個額外的黑節點，這個是很難調整的。但是設為紅色節點後，可能會導致出現兩個連續紅色節點的衝突，那麼可以通過顏色調換（color flips）和樹旋轉來調整。）下面要進行什麼操作取決於其他臨近節點的顏色。同人類的家族樹中一樣，我們將使用術語叔父節點來指一個節點的父節點的兄弟節點。注意：- 性質1和性質3總是保持著。- 性質4-{}-只在增加紅色節點、重繪黑色節點為紅色，或做旋轉時受到威脅。- 性質5-{}-只在增加黑色節點、重繪紅色節點為黑色，或做旋轉時受到威脅。
在下面的示意圖中，將要插入的節點標為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);
}
注意：在下列情形下（情形3、4和5）我們假定新節點的父節點為紅色，所以它有祖父節點；因為如果父節點是根節點，那父節點就應當是黑色。所以新節點總有一個叔父節點，儘管在情形4和5下它可能是葉子節點。
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中的左和右應當對調。
void insert_case4(node *n){
if(n == n->parent->right && n->parent == grandparent(n)->left) {
rotate_left(n);
n = n->left;
} else if(n == n->parent->left && n->parent == grandparent(n)->right) {
rotate_right(n);
n = n->right;
}
insert_case5 (n);
}
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(n->parent);
} else {
/* Here, n == n->parent->right && n->parent == grandparent (n)->right */
rotate_left(n->parent);
}
}
注意插入實際上是原地算法，因為上述所有調用都使用了尾部遞歸。

**刪除**
如果需要刪除的節點有兩個兒子，那麼問題可以被轉化成刪除另一個只有一個兒子的節點的問題（為了表述方便，這裡所指的兒子，為非葉子節點的兒子）。對於二叉查找樹，在刪除帶有兩個非葉子兒子的節點的時候，我們要麼找到它左子樹中的最大元素、要麼找到它右子樹中的最小元素，並把它的值轉移到要刪除的節點中（如在這裡所展示的那樣）。我們接著刪除我們從中複製出值的那個節點，它(複製出值的節點)必定有少於兩個非葉子的兒子。因為只是複製了一個值（沒有複製顏色），不違反任何性質，這就把問題簡化為如何刪除最多有一個兒子的節點的問題。它不關心這個節點是最初要刪除的節點還是我們從中複製出值的那個節點。
在本文餘下的部分中，我們只需要討論刪除只有一個兒子的節點（如果它兩個兒子都為空，即兩個Nil節點，我們任意將其中一個看作它的兒子）。如果我們刪除一個紅色節點（此時該節點的兒子皆為Nil節點），它的父親和兒子一定是黑色的(紅黑樹性質4)。所以我們可以簡單的用它的黑色兒子替換它，並不會破壞性質3和性質4。通過被刪除節點的所有路徑只是少了一個紅色節點，這樣可以繼續保證性質5。另一種簡單情況是在被刪除節點是黑色而它的兒子是紅色的時候。如果只是去除這個黑色節點，用它的紅色兒子頂替上來的話，會破壞性質5，但是如果我們重繪它的兒子為黑色，則曾經通過它的所有路徑將通過它的黑色兒子，這樣可以繼續保持性質5。
需要進一步討論的是在要刪除的節點和它的兒子二者都是黑色的時候，這是一種複雜的情況（這種情況下該節點的兩個兒子都是葉子節點，否則若其中一個兒子是黑色非葉子節點，另一個兒子是葉子節點，那麼從該節點通過非葉子節點兒子的路徑上的黑色節點數最小為2，而從該節點到另一個葉子節點兒子的路徑上的黑色節點數為1，違反了性質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);
}
注意：在情形2、5和6下，我們假定N是它父親的左兒子。如果它是右兒子，則在這些情形下的左和右應當對調。
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);
}
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);
}
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);
}
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);
}
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次）的旋轉。

**C++示例代碼**
#define NIL   NULL
#define BLACK 1
#define RED 0
#include 
using namespace std;
class bst {
private:
struct Node {
int value;
bool color;
Node *leftTree, *rightTree, *parent;
Node() : value(0), color(RED), leftTree(NULL), rightTree(NULL), parent(NULL) { }        
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 value 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 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;
}else{
return false;
}
}
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());
rotate_left(p->parent);
else
//rotate_right(p->sibling());
rotate_right(p->parent);
}
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);
p->color = BLACK;
p->parent->color = RED;
rotate_right(p);
} else if(p->parent->leftTree == p && p->grandparent()->rightTree == p->parent) {
rotate_right(p);
p->color = BLACK;
p->parent->color = RED;
rotate_left(p);
} 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 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個內部節點的紅黑樹的高度是。
定義：- 表示以節點為根的子樹的高度。- 表示從到子樹中任何葉子的黑色節點的數目（如果是黑色則不計數它，也叫做黑色高度）。
引理：以節點為根的子樹有至少個內部節點。
引理的證明（通過歸納高度）：
基礎：
如果的高度是零則它必定是NIL，因此。所以：
歸納假設：的有個內部節點暗示了h() = k+1的有個內部節點。
因為有h()> 0所以它是個內部節點。同樣的它有黑色高度要麼是bh()要麼是bh()-1（依據是紅色還是黑色）的兩個兒子。通過歸納假設每個兒子都有至少個內部接點，所以有：
個內部節點。
使用這個引理我們現在可以展示出樹的高度是對數性的。因為在從根到葉子的任何路徑上至少有一半的節點是黑色（根據紅黑樹性質4），根的黑色高度至少是。通過引理我們得到：
因此根的高度是。

**參見**
 AVL樹- B樹- 跳躍列表 一種有可能替代平衡樹的數據結構- dancing tree- 伸展樹- 2-3-4樹- Treap

**引用**
  Mathworld: Red-Black Tree date=20080430084343 -  San Diego State University: CS 660: Red-Black tree notes date=20080509090310 , by Roger Whitney- Cormen, Leiserson, Rivest, Stein. Introduction to Algorithms. Massachusetts: The MIT Press, 2002. pp273-77. ISBN 0-07-013151-1

**外部鏈接**
  An applet + quick explanation date=20050914232749 -  Red/Black Tree Demonstration date=20210421014929 -  An example（animated GIF, 200KB）-  An example（static picture）-  Another explanation（pictures, source code, and Java interactive animation）-  Red-Black Tree Demonstration by David M. Howard-  RBT: A SmallEiffel Red-Black Tree Library date=20171007184333 -  libredblack: A C Red-Black Tree Library date=20210418101446 - Data Structures and Algorithm Analysis in C (2nd edition) date=20201109001448 -  Red-Black Tree C++ Code-  Red-Black Trees by Thomas Niemann-  紅黑樹的介紹和實現