算法學習_紅黑樹


二叉搜索樹由於其隨機構造的特點,可能會出現構造出來的搜索樹效率十分低。

算法學習--二叉搜索樹: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是一個指針,不要弄成野指針了。




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