标签:
红黑树是一棵二叉搜索树,他在每个结点上增加了一个存储位为来标识结点的颜色,可以是RED或者BLACK。通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,保证没有一条路径比其他路径长出2倍,所以是近似平衡的。
- 每个节点是红色或者黑色
- 根节点是黑色
- 每个叶节点(NULL)是黑色的
- 如果一个节点是红色的,则它的两个子节点都是黑色的
- 对于每个结点,从该结点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点
一定不会有两个红的节点相连,但是
会不会有两个黑的节点相连?理论上是可以的,如果你觉得构造不出来,我们假设一棵全黑的完全二叉树,也符合上述性质,虽然这样失去了红黑树的意义。
之所以我会想说上面的问题,我想到,黑节点的父节点一定是红的吗?看来也不一定。
(主观臆断,如有大神路过,发现错误请指正,谢啦)
#define RED 0
#define BLACK 1
typedef struct RBTreeNode {
bool color;
int value;
RBTreeNode* left;
RBTreeNode* right;
RBTreeNode* parent;
RBTreeNode(){
color = BLACK;
value = 0;
left = right = parent = NULL;
}
}RBTreeNode;
RBTreeNode* Tnull;
int main() {
Tnull = new RBTreeNode; // initialed by construct function
return 0;
}
为了便于处理边界条件,所有为空的指针都指向一个哨兵(Tnull),它是一个黑色节点,其他属性不重要;
从某个节点x出发,到达一个叶子节点(此时的叶子节点就为哨兵(Tnull))的任意一条简单路径里黑色节点的个数
一棵有n个内部节点的红黑树的高度至多为
旋转操作只是辅助插入和删除节点的操作,所以此时不用考虑颜色的变化,专注于指针的变化情况就好
可以参照《算法导论》第三版中文版的图13-2
//左旋
void LeftRotate(RBTreeNode* &root, RBTreeNode* x) {
if (x == NULL) {
cout << "Wrong input!\n";
return;
}
RBTreeNode* y = x->right; //暂存旋转前x的右子树
if (y == NULL) {
return;
}
x->right = y->left; //设置旋转后x的右子树
if (y->left != Tnull){
y->left->parent = x; //设置旋转后x的右子树的双亲
}
y->parent = x->parent;
//先设置旋转后y的双亲是因为此时x的双亲还没有变
//让旋转前x的双亲指向y
if (x->parent == Tnull) { //x旋转前是树根
root = y;
} else if (x == x->parent->left) { //x旋转前是双亲的左子树
x->parent->left = y;
} else if (x == x->parent->right) { //x旋转前是双亲的右子树
x->parent->right = y;
}
y->left = x; //设置旋转后y的左子树
x->parent = y; //设置旋转后x的双亲为旋转后的y
}
可以参照注释,分析一下设置的节点的顺序,我们根据这个思路可以写出右旋:
//右旋
void RightRotate(RBTreeNode* &root, RBTreeNode* y) {
if (y == NULL) {
cout << "Wrong input!\n";
return;
}
RBTreeNode* x = y->left; //暂存旋转前y的左子树
if (x == NULL) {
return;
}
y->left = x->right; //设置旋转后y的左子树
if (x->right != Tnull){
x->right->parent = y; //设置旋转后y的左子树的双亲
}
y->parent = x->parent;
//让旋转前y的双亲指向x
if (y->parent == Tnull) { //y旋转前是树根
root = x;
} else if (y == y->parent->left) { //y旋转前是双亲的左子树
y->parent->left = x;
} else if (y == y->parent->right) { //y旋转前是双亲的右子树
y->parent->right = x;
}
x->right = y; //设置旋转后x的右子树
y->parent = x; //设置旋转后y的双亲为x
}
为什么新插入的节点要标记为红色?如果不这样会增加黑高,从而违反性质5
这里的函数处理边界情况和异常情况会比较麻烦….
分析情况可以参考《算法导论》和一篇我看到的比较好的博文,结合在一起;
博文:http://www.cnblogs.com/xuqiang/archive/2011/05/16/2047001.html
我也在代码中加入了很多注释,实现过程是基于《算法导论》的伪码,希望可以帮助理解
代码:
插入过程:
//先在外面创建好节点z再插入,过程很像二叉搜索树的插入
void Insert(RBTreeNode* &root, RBTreeNode* z) {
RBTreeNode* y = Tnull;
RBTreeNode* x = root;
while(x && x != Tnull) { //用x遍历去找合适的插入位置, y始终保持是x的双亲
y = x;
if (z->value < x->value) {
x = x->left;
} else {
x = x->right;
}
}
z->parent = y;
if (y == Tnull) {
root = z;
} else if (z->value < y->value) {
y->left = z;
} else {
y->right = z;
}
z->left = z->right = Tnull;
z->color = RED;
//InOrder(root);
InsertFixUp(root, z);
}
插入之后的维护过程:
void InsertFixUp(RBTreeNode* & root, RBTreeNode* &z) {
while(z->parent && z->parent->color == RED) { //因为z的颜色被设置为红色,此时违反性质4
if (z->parent->parent != Tnull&& z->parent == z->parent->parent->left) {
//插入点的父节点是爷爷节点的左子树
RBTreeNode* y = z->parent->parent->right; //y是叔叔节点
if (y->color == RED) { //case 1 叔叔节点是红色
z->parent->color = BLACK; //父亲节点
y->color = BLACK; //叔叔节点
z->parent->parent->color = RED; //爷爷节点
z = z->parent->parent; //此时爷爷节点有可能也违反规则,我们尾递归上去
} else if (z == z->parent->right) { //case 2 叔叔节点是黑色
z = z->parent;
LeftRotate(root, z); //如果插入节点是父节点的右子树,旋过去
}
if (z->parent != Tnull) {
z->parent->color = BLACK;
if (z->parent->parent != Tnull) {
z->parent->parent->color = RED;
RightRotate(root, z->parent->parent);
}
}
} else if (z->parent->parent != Tnull && z->parent == z->parent->parent->right){
//插入点的父节点是爷爷节点的右子树
RBTreeNode* y = z->parent->parent->left; //y是叔叔节点
if (y->color == RED) { //case 1 叔叔节点是红色
z->parent->color = BLACK; //父亲节点
y->color = BLACK; //叔叔节点
z->parent->parent->color = RED; //爷爷节点
z = z->parent->parent; //此时爷爷节点有可能也违反规则,我们尾递归上去
} else if (z == z->parent->left) { //case 2 叔叔节点是黑色
z = z->parent;
RightRotate(root, z); //如果插入节点是父节点的左子树,旋过去
}
if (z->parent != Tnull) {
z->parent->color = BLACK;
if (z->parent->parent != Tnull ) {
z->parent->parent->color = RED;
LeftRotate(root, z->parent->parent);
}
}
}
}
root->color = BLACK;
}
看到书上说与插入相比,删除操作要稍微复杂些。。。。。。
首先一个替换的子例程:
//以v为根的子树替换一棵以u为根的子树
void TransPlant(RBTreeNode* & root, RBTreeNode* u, RBTreeNode* & v) {
if (u->parent == Tnull) {
root = v;
} else if (u == u->parent->left) {
u->parent->left = v;
} else {
u->parent->right = v;
}
v->parent = u->parent;
}
然后是删除的过程:
void Delete(RBTreeNode* & root, RBTreeNode* z) {
if (root == NULL) {
cout << "Already empty!" << endl;
return;
}
if (z == NULL) {
cout << "Not exist!" << endl;
return;
}
// y节点有两种情况:
// 1. z节点删除后,y节点是要在树中去替代z节点的节点
// 2. y节点指向被删除的z节点
// 但是无论哪种情况,如果y的原来的颜色为黑色,那么就会引起红黑树性质的破坏
RBTreeNode* y = z; //指向待删除节点
RBTreeNode* x; //x来记录删除后(移动到y节点的原始位置)的节点的位置
bool y_original_color = y->color; //记录原来待删除节点的颜色
//以下两个case是待删除节点只有一个子树或者没有子树
if (z->left == Tnull) {
x = z->right;
TransPlant(root, z, z->right);
} else if (z->right == Tnull) {
x = z->left;
TransPlant(root, z, z->left);
} else { //待删除节点有两个子树
y = Min(z->right); //记录待删除节点的后继
y_original_color = y->color; //记录待删除节点的后继的颜色
x = y->right;
if (y->parent == z) { //说明后继是待删除节点的右子树的树根
x->parent = y;
} else { //把后继换到右子树的树根
TransPlant(root, y, y->right);
y->right = z->right;
y->right->parent = y;
}
TransPlant(root, z, y);
y->left = z->left;
y->left->parent = y;
y->color = z->color;
}
if (y_original_color == BLACK)
DeleteFixUp(root, x);
}
我在注释里已经写了x代表什么和y代表什么,既然是y的移动有可能改变红黑树的性质,那么DeleteFixUp() 的参数为什么是x呢?
y原来的颜色为黑色:
如果y为删除的节点,那么少了一个黑节点,y将自己的颜色下推给x;
如果y是z的后继,要去替代z,那么我们将y的颜色设置为z原来的颜色(上面代码第38行),为了维持红黑树的性质,还是将自己的颜色下推给了x;
然后x就变成了红黑色或者双重黑色
那么也就是说删除后,树的其他性质虽然保持,但是x这个节点违反了性质1(因为有双重颜色),所以我们FixUp的关键就在于x,所以参数是x;
DeleteFixUp:
void DeleteFixUp(RBTreeNode* & root, RBTreeNode* x) {
RBTreeNode* w; //保持w为x的兄弟
while(x != root && x->color == BLACK) {
if (x == x->parent->left) {
w = x->parent->right;
if (w->color == RED) { //case 1 w的颜色为红色
w->color = BLACK;
x->parent->color = RED;
LeftRotate(root, x->parent);
w = x->parent->right;
}
if (w->left->color == BLACK && w->right->color == BLACK) { //case 2
w->color = RED;
x = x->parent; //向上尾递归
}
else {
if (w->right->color == BLACK) { //case 3 w的右子树为黑色
w->left->color = BLACK;
w->color = RED;
RightRotate(root, w);
w = x->parent->right;
}
w->color = x->parent->color;
x->parent->color = BLACK;
w->right->color = BLACK;
LeftRotate(root, x->parent);
x = root;
}
} else {
w = x->parent->left;
if (w->color == RED) { //case 1 w的颜色为红色
w->color = BLACK;
x->parent->color = RED;
RightRotate(root, x->parent);
w = x->parent->left;
}
if (w->left->color == BLACK && w->right->color == BLACK) { //case 2
w->color = RED;
x = x->parent; //向上尾递归
}
else {
if (w->left->color == BLACK) { //case 3 w的左子树为黑色
w->right->color = BLACK;
w->color = RED;
LeftRotate(root, w);
w = x->parent->left;
}
w->color = x->parent->color;
x->parent->color = BLACK;
w->left->color = BLACK;
RightRotate(root, x->parent);
x = root;
}
}
}
x->color = BLACK;
}
Debug了好几天,终于写完了。
如果想测试的话,可以去这里:
https://github.com/preke/DataStructure/blob/master/RedBlackTree.c%2B%2B
这里有完整的代码(包括中序、前序遍历,查找,最小值等等)。
标签:
原文地址:http://blog.csdn.net/u013398398/article/details/52296151