红黑树,一种平衡二叉树,最为著名的应用就是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;
}
复制代码
节点颜色对于查找过程没有意义,红黑树查找过程和二叉树是同样的。