红黑树

Posted zhchoutai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了红黑树相关的知识,希望对你有一定的参考价值。


性质:

红黑树是一棵二叉搜索树,他在每一个结点上添加了一个存储位为来标识结点的颜色。能够是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
这里有完整的代码(包括中序、前序遍历,查找,最小值等等)。

以上是关于红黑树的主要内容,如果未能解决你的问题,请参考以下文章

[转]红黑树讲解

STL详解—— 用一棵红黑树同时封装出map和set

红黑树 实现

红黑树探索笔记

码图并茂红黑树

C++ 红黑树