紅黑樹,一種平衡二叉樹,最爲著名的應用就是C++ STL中的map,是有序集合最爲理想的存儲方式之一。除了二叉樹所具備的屬性以後,紅黑樹中每一個節點多了一個「顏色」屬性,能夠是紅色或者是黑色。一棵紅黑樹應該知足一下的性質:c++
根據定義,能夠寫出紅黑樹節點的數據結構:git
struct Node {
enum Color { RED, BLACK };
Color _color;
Key _key;
Value _value;
Node *_parent, *_left, *_right;
Node(): _color(BLACK) {}
Node(const Key &key, const Value &value, Node *left, Node *right, Node *parent):
_key(key), _value(value), _color(RED), _left(left), _right(right), _parent(parent) {}
};
複製代碼
本文省略了拷貝、析構、賦值等操做,完整源代碼放在Gist上。github
想要紅黑樹維持着平衡,就須要在插入元素和刪除元素的過程當中不斷對結構進行調整。其中,最基礎的操做就是左旋和右旋。數據結構
以左旋爲例,操做能夠分爲三步:ui
void leftRotate(Node *x) {
Node *y = x->_right;
// remove y->left to x->right
x->_right = y->_left;
if (x->_right != nil)
x->_right->_parent = x;
// remove y up
y->_parent = x->_parent;
if (x->_parent == nil)
root = y;
else if (x->_parent->_left == x)
x->_parent->_left = y;
else
x->_parent->_right = y;
// remove x down
x->_parent = y;
y->_left = x;
}
複製代碼
先污染,後治理。首選按照二叉樹的插入方法先將節點插入二叉樹,而後再對紅黑樹進行調整。spa
void insert(Node *nptr) {
Node *it = root, *p = root;
// find insert position
while (it != nil) {
p = it;
if (nptr->_key < it->_key)
it = it->_left;
else if (nptr->_key > it->_key)
it = it->_right;
else {
// find target key-value
it->_value = nptr->_value;
return;
}
}
// insert
nptr->_parent = p;
if (p == nil)
root = nptr;
else if (nptr->_key < p->_key)
p->_left = nptr;
else
p->_right = nptr;
// fixup
insertFixup(nptr);
}
複製代碼
在完成插入以後,將面臨六種狀況,因爲存在左右對稱的狀況,實際上只須要考慮三種狀況。3d
在狀況1時,調整目標節點B的父節點和叔節點都是紅節點,祖父節點爲黑節點,咱們須要將父節點和叔節點的顏色改爲黑色,祖父節點設爲紅節點,並將調整目標設爲祖父節點。code
在狀況2和狀況3中,新插入節點的父節點爲紅節點,祖父節點爲黑節點,而且叔節點爲黑節點。首選須要把狀況2轉換成狀況3,讓B成爲黑節點,A和C爲B的紅色子節點,並將調整目標設爲A。每每在處理完這兩種狀況後,紅黑樹完成了調整。cdn
因爲NIL也算是黑色節點,因此還須要定義一個獲取節點顏色的宏。blog
調整過程是自下而上的,一個插入的新節點的顏色是紅色的,每次調整的目標是使每一個子樹保持紅黑樹的性質。對於狀況2和3來講,調整結束後子樹樹根爲黑色,對總體的性質不會形成影響。而在狀況1中,子樹樹根爲紅色,對總體的性質形成了影響,須要繼續調整,這就是循環條件的意義。
void insertFixup(Node *ptr) {
while (ptr->_parent->_color == Node::RED) {
if (ptr->_parent == ptr->_parent->_parent->_left) {
Node *y = ptr->_parent->_parent->_right;
// case 1
if (y->_color == Node::RED) {
ptr->_parent->_color = Node::BLACK;
y->_color = Node::BLACK;
ptr->_parent->_parent->_color = Node::RED;
ptr = ptr->_parent->_parent;
} else {
// case 2: switch case 2 to case 3
if (ptr == ptr->_parent->_right) {
ptr = ptr->_parent;
leftRotate(ptr);
}
// case 3
ptr->_parent->_color = Node::BLACK;
ptr->_parent->_parent->_color = Node::RED;
rightRotate(ptr->_parent->_parent);
}
} else {
// with 'left' and 'right' exchanged
Node *y = ptr->_parent->_parent->_left;
if (y->_color == Node::RED) {
ptr->_parent->_color = Node::BLACK;
y->_color = Node::BLACK;
ptr->_parent->_parent->_color = Node::RED;
ptr = ptr->_parent->_parent;
} else {
if (ptr == ptr->_parent->_left) {
ptr = ptr->_parent;
rightRotate(ptr);
}
ptr->_parent->_color = Node::BLACK;
ptr->_parent->_parent->_color = Node::RED;
leftRotate(ptr->_parent->_parent);
}
}
}
root->_color = Node::BLACK;
}
複製代碼
刪除的過程比插入過程複雜不少。與二叉樹同樣,咱們須要一個替換操做,將子樹u替換成子樹v。
void transplant(shared_ptr<Node> u, shared_ptr<Node> v) {
if (u->_parent == nil)
root = v;
else if (u == u->_parent->_left)
u->_parent->_left = v;
else
u->_parent->_right = v;
v->_parent = u->_parent;
}
複製代碼
刪除的過程和二叉樹相似,多了一些處理哨兵、記錄顏色的過程。
void remove(Node *ptr) {
Node *y = ptr, *x;
int y_original_color = y->_color;
if (y->_left == nil) {
x = ptr->_right;
transplant(ptr, ptr->_right);
} else if (y->_right == nil) {
x = ptr->_left;
transplant(ptr, ptr->_left);
} else {
y = min(ptr->_right);
y_original_color = y->_color;
x = y->_right;
if (y->_parent == ptr)
x->_parent = y; // change nil->_parent
else {
transplant(y, y->_right);
y->_right = ptr->_right;
y->_right->_parent = y;
}
transplant(ptr, y);
y->_left = ptr->_left;
y->_left->_parent = y;
y->_color = ptr->_color;
}
if (y_original_color == Node::BLACK)
deleteFixup(x);
}
複製代碼
首先考慮一下刪除節點的時候,咱們對紅黑樹形成了什麼樣的影響。**若是刪除一個紅色節點,那麼紅黑樹的性質並不會收到任何影響;若是刪除的是一個黑色節點,那麼意味着黑高相等的性質將不復存在,刪除一個黑色節點。**那麼和插入調整的思路相似,每次調整的目標是保持子樹內的性質。
調整過程須要分左右對稱的四種狀況:
而對於循環條件。應該怎麼理解呢?若是調整過程到了根節點,那麼就不存在某子樹內黑高缺一的狀況,能夠結束循環。若是遇到了紅節點,那麼把這個紅節點變成黑節點就能夠解決黑高不等的狀況。
void removeFixup(Node *ptr) {
while (ptr != root && ptr->_color == Node::BLACK) {
if (ptr == ptr->_parent->_left) {
Node *w = ptr->_parent->_right;
// case 1
if (w->_color == Node::RED) {
w->_color = Node::BLACK;
ptr->_parent->_color = Node::RED;
leftRotate(ptr->_parent);
w = ptr->_parent->_right;
}
// case 2
if (w->_left->_color == Node::BLACK && w->_right->_color == Node::BLACK) {
w->_color = Node::RED;
ptr = ptr->_parent;
} else {
// case 3
if (w->_right->_color == Node::BLACK) {
w->_left->_color = Node::BLACK;
w->_color = Node::RED;
rightRotate(w);
w = ptr->_parent->_right;
}
// case 4
w->_color = ptr->_parent->_color;
ptr->_parent->_color = Node::BLACK;
w->_right->_color = Node::BLACK;
leftRotate(ptr->_parent);
ptr = root;
}
} else {
// with 'left' and 'right' exchanged
Node *w = ptr->_parent->_left;
if (w->_color == Node::RED) {
w->_color = ptr->_parent->_color;
ptr->_parent->_color = Node::RED;
rightRotate(ptr->_parent);
w = ptr->_parent->_left;
}
if (w->_left->_color == Node::BLACK && w->_right->_color == Node::BLACK) {
w->_color = Node::RED;
ptr = ptr->_parent;
} else {
if (w->_left->_color == Node::BLACK) {
w->_color = Node::RED;
w->_right->_color = Node::BLACK;
leftRotate(w);
w = ptr->_parent->_left;
}
w->_color = ptr->_parent->_color;
w->_left->_color = Node::BLACK;
ptr->_parent->_color = Node::BLACK;
rightRotate(ptr->_parent);
ptr = root;
}
}
}
ptr->_color = Node::BLACK;
}
複製代碼
節點顏色對於查找過程沒有意義,紅黑樹查找過程和二叉樹是同樣的。