算法学习_红黑树


二叉搜索树由于其随机构造的特点,可能会出现构造出来的搜索树效率十分低。

算法学习--二叉搜索树:http://blog.csdn.net/hermit_inwind/article/details/50545703


红黑树是多种平衡二叉搜索树中的一种,一颗含有n个结点的红黑树其高度接近O(lgn);由于二叉搜索树上的动态集合操作耗时与其高度有关,所以,我们可以知道,红黑树上动态集合操作平均耗时为O(lgn),效率很高。


红黑树结点的基本构成:p(双亲结点),left(左子结点),right(右子结点),key(查询关键字),color(颜色)。
红黑树基本性质:
  一个红黑树结点的颜色为红色或为黑色。
红色结点的子结点一定为黑色
叶子结点为黑色(其中叶子结点为哨兵结点NIL替代NULL)
根节点为黑色
任意结点到其任何一个后代叶子结点构成的简单路径上黑色结点的数量相等
从结点x出发,到其一个后代叶子结点的简单路径上的黑色结点个数(不包括该结点)称为黑高(由上一个基本性质可知,任意结点的黑高固定)。


红黑树的基本操作和普通的二叉搜索树相同,为插入,删除,查询。其中查询的操作和二叉搜索树完全相同。但为了维护红黑树的性质,红黑树的插入和删除的实现和一般的二叉搜索树不同。其中需要使用到Rotate()函数来辅助完成。Rotate旋转函数分为Left_Rotate左旋和Right_Rotate右旋。

左旋函数代码:

void Left_Rotate(Node *root,Node *x)   //左旋
{
    Node *y=x->right;
    x->right=y->left;
    if (y->left!=NIL)
        y->left->p=x;
    y->p=x->p;
    if (x->p==NIL)
        root=y;
    else if (x->p->left==x)
    {
        x->p->left=y;
    }
    else
    {
        x->p->right=y;
    }
    y->left=x;
    x->p=y;
}
左旋效果如图:

右旋代码:

void Right_Rotate(Node *root,Node *x)   //右旋
{
    Node *y=x->left;
    x->left=y->right;
    if (y->right!=NIL)
    {
        y->right->p=x;
    }
    y->p=x->p;
    if (x->p==NIL)
        root=y;
    else if (x->p->left==x)
        x->p->left=y;
    else
        x->p->right=y;
    y->right=x;
    x->p=y;
}
效果为上图从右至左。
旋转改变了原本指针的指向,但依旧能保证二叉搜索树的性质(并不一定能够保证红黑树的性质),在后面的Fixup函数中,将会用到旋转来维护因插入新结点而遭到破坏的红黑树。


红黑树的插入函数Rb_Insert()与二叉搜索树的插入大致相同,但因为原本在二叉搜索树中的NULL在红黑树用使用哨兵结点NIL替代,所以有些部分不一样。

void Rb_Insert(Node *root,Node *z)
{
    Node *x=root;
    Node *y=NIL;
    while (x!=NIL)
    {
        y=x;
        if (z->key<x->key)
            x=x->left;
        else
            x=x->right;
    }
    z->p=y;
    if (z->p==NIL)
        root=z;
    else if (z->key<y->key)
        y->left=z;
    else
        y->right=z;
    z->left=NIL;
    z->right=NIL;
    z->color=RED;
    Rb_Insert_Fixup(root,z);
}
在Insert函数的最后我们看到有一个Rb_Insert_Fixup()函数,这是用来维护红黑树性质的。在这里再提一下红黑树的基本性质(感觉非常重要,而博主在学习的过程中时不时的会忘记)
1.红黑树每个结点为红色或者黑色
2.红色结点的子结点一定为黑色结点
3.叶子结点一定为黑色结点
4.根节点为黑色
5.任意结点到其任何一个后代叶子结点构成的简单路径上黑色结点的数量相等
在插入一个新结点后,因为新结点的颜色人为设置为红色,所以性质1,3,5均不会被破坏。只有可能出现的情况是:
1.插入结点为根结点,然而该结点为红色。
2.插入结点的双亲结点也为红色。
其中,第二种情况又可以分类,稍微麻烦一些,这里用代码来表现对情况的分类可能会更加清晰一些

void Rb_Insert_Fixup(Node *root,Node *z)
{
    while (z->p->color==RED)
    {
        if (z->p==z->p->p->left)
        {
            y=z->p->p->right;
            if (y->color==RED)
            {
                z->p->color=BLACK;
                y->color=BLACK;
                z->p->p->color=RED;
                z=z->p->p;
            }
            else if (z==z->p->right)
            {
                z=z->p;
                Left_Rotate(root,z);
            }
            z->p->color=BLACK;
            z->p->p->color=RED;
            Right_Rotate(root,z->p->p);
        }
        else
        {
            y=z->p->p->left;
            if (y->color==RED)
            {
                z->p->color=BLACK;
                y->color=BLACK;
                z->p->p->color=RED;
                z=z->p->p;
            }
            else if (z==z->p->left)
            {
                z=z->p;
                Right_Rotate(root,z);
            }
            z->p->color=BLACK;
            z->p->p->color=RED;
            Left_Rotate(root,z->p->p);
        }
    }
    root->color=BLACK;
}
只有在z的双亲结点为红色时才会进行旋转,保证了红黑树颜色的性质。


删除后保持红黑树性质的思路与插入后保持红黑树性质大致相同,但是为了分情况,在删除的时候需要记录替换删除结点z的结点y原本的颜色,还需要记录替换y结点的结点x,用于追踪x的去向。因此,在编写Rb_Delete()的时候需要加上相应的代码。

void Rb_Delete(Node *root,Node z)
{
    Node *y=z;
    int YOC=y->color;   //y_original_color
    if (z->left==NIL)
    {
        x=z->right;
        Rb_Transplant(z,z->right);
    }
    else if (z->right==NIL)
    {
        x=z->left;
        Rb_Transplant(root,z,z->left);
    }
    else
    {
        y=Minimum(z->right);
        YOC=y->color;
        x=y->right;
        if (y->p==z)
            x->p=y;
        else
        {
            Rb_Transplant(root,y,y->right);
            y->right=z->right;
            y->right->p=y;
        }
        Rb_Transplant(root,z,y);
        y->left=z->left;
        y->left->p=y;
        y->color=z->color;
    }
    if (YOC==BLACK)
        Rb_Delete_Fixup(root,x);
    free(z);
}

显然,进行删除操作后如果y结点的颜色为黑色,红黑树原本的性质会被破坏。
若y结点颜色为红色,使用y结点替换z后子树黑高不会改变。当z结点存在两个子结点的时候,留意最后的语句:y->color=z->color;将替换z后的y的颜色更改为与z一样的颜色,所以红黑树性质也不会改变(当时博主把这里一下给略过去了,后来自己画图纠结了很久)。
接下来就是使用Rb_Delete_Fixup(root,x)来维护红黑树的性质。


可能出现的情况有三个:
1.y指向根节点,然后y的一个红色的子结点成为新的根结点,根结点为黑色的性质被破坏。
2.x和x->p均为红色,红色结点的子结点一定为黑色的性质被破坏。
3.y原本为黑色结点,替换以后,原本包含y的路径上黑高会减一,从一结点出发到达其任意后继叶结点的简单路径上黑色结点数量相等的性质会被破坏。


所以Rb_Delete_Fixup()将解决以上问题,其中第一个问题最简单,只需要在解决其他问题后将x指向根结点然后将该结点染黑即可。然后解决其他两个问题时还需要进一步分类。
当x结点为红色的时候,只需要将x结点染黑,补上原本y移动而减少的黑高即可。(注意到x一定为y的子结点,且是进行删除操作后替代原本y的结点)
当x结点为黑色且不为根结点的时候需要进一步分类。先使用w变量记录x的兄弟结点。那么情况有四种1.w为红色。2.w为黑色,其两个子结点均为黑色。3.w为黑色,其左子结点为红色,右子结点为黑色。4.w为黑色,其右子结点为红色。

Rb_Delete_Fixup代码:

void Rb_Delete_Fixup(Node *root,Node *x)
{
    while (x!=root&&x->color==BLACK)
    {
        if (x==x->p->left)
        {
            Node *w=x->p->right;
            if (w->color==RED)
            {
                w->color=BLACK;
                x->p->color=RED;
                Left_Rotate(root,x->p);
                w=x->p->right;
            }
            if (w->left->color==BLACK&&w->right->color==BLACK)
            {
                w->color=RED;
                x=x->p;
            }
            else
            {
                if (w->right->color==BLACK)
                {
                    w->left->color=BLACK;
                    w->color=RED;
                    Right_Rotate(root,w);
                    w=x->p->right;
                }
                w->color=x->p->color;
                x->p->color=BLACK;
                w->right->color=BLACK;
                Left_Rotate(root,x->p);
                x=root;
            }
        }
        else
        {
            Node *w=x->p->left;
            if (w->color==RED)
            {
                w->color=BLACK;
                x->p->color=RED;
                Left_Rotate(root,x->p);
                w=x->p->left;
            }
            if (w->left->color==BLACK&&w->right->color==BLACK)
            {
                w->color=RED;
                x=x->p;
            }
            else
            {
                if (w->left->color==BLACK)
                {
                    w->right->color=BLACK;
                    w->color=RED;
                    Left_Rotate(root,w);
                    w=x->p->left;
                }
                w->color=x->p->color;
                x->p->color=BLACK;
                w->left->color=BLACK;
                Right_Rotate(root,x->p);
                x=root;
            }
        }
    }
    x->color=BLACK;
}
最后再说一下,注意NIL是一个指针,不要弄成野指针了。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章