码迷,mamicode.com
首页 > 其他好文 > 详细

红黑树

时间:2016-08-25 21:28:50      阅读:184      评论:0      收藏:0      [点我收藏+]

标签:


性质:

红黑树是一棵二叉搜索树,他在每个结点上增加了一个存储位为来标识结点的颜色,可以是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),它是一个黑色节点,其他属性不重要;

黑高(black-height):

从某个节点x出发,到达一个叶子节点(此时的叶子节点就为哨兵(Tnull))的任意一条简单路径里黑色节点的个数

一棵有n个内部节点的红黑树的高度至多为2log(n+1)

旋转:

旋转操作只是辅助插入和删除节点的操作,所以此时不用考虑颜色的变化,专注于指针的变化情况就好
可以参照《算法导论》第三版中文版的图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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!