程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> 算法導論 之 紅黑樹

算法導論 之 紅黑樹

編輯:關於C

1 引言

在《算法導論 之 紅黑樹 - 插入》中已經對紅黑樹的5個性質做了較詳細的分析,同時也給出了insert操作的C語言實現。首先我們再回顧一下紅黑樹的5個性質:

①、每個節點要麼是紅色的,要麼是黑色的;

②、根結點是黑色的;

③、所有葉子結點(NIL)都是黑色的;

④、如果一個結點是紅色,則它的兩個兒子都是黑色的;

⑤、對任何一個結點,從該結點通過其子孫結點到達葉子結點(NIL)的所有路徑上包含相同數目的黑結點。

和插入操作一樣,結點的刪除操作的時間復雜度也是O(log2@N)[注:以2為底數,N為對數],但刪除操作的處理更復雜一些。

2 刪除處理

2.1 刪除過程

假如需要刪除結點D,則刪除操作的過程有如下幾種情況:[注:在以下所有繪制的紅黑樹中,均未繪制葉子結點]

情況1:被刪結點D的左孩子為葉子結點,右孩子無限制(可為葉子結點,也可為非葉子結點)

處理過程:刪除結點D,並用右孩子結點替代結點D的位置。如果被刪結點D為紅色,則紅黑樹性質未被破壞,因此無需做其他調整;如果被刪結點D為黑色,則需進一步做調整處理。

\

圖1 情況1-1:左右孩子均為葉子結點<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+W9K219O94bXjyKG0+sHLveG140S1xM671sNdPC9wPgoKPGJyPgoKPGltZyBzcmM9"/uploadfile/Collfiles/20140118/20140118090313114.jpg" alt="\">

圖2 情況1-2:左孩子為葉子結點 右孩子不為葉子結點

[結點DR取代了葉子結點的位置]

情況2: 被刪結點D的右孩子為葉子結點,左孩子不為葉子結點

處理過程:刪除結點D,並用左孩子節點替代結點D的位置。如果被刪結點D為紅色,則紅黑樹性質未被破壞,因此不需做其他調整;如果被刪結點D為黑色, 則需進一步做調整處理。

\

圖3 情況2:右孩子為葉子結點 左孩子不為葉子結點

[結點DL取代結點D的位置]

情況3: 被刪結點D的左右孩子均不為葉子節點

處理過程:找到結點D的後繼結點S,將結點S的key值賦給結點D,再將結點S從樹中刪除,並用結點S的右孩子替代結點S的位置。從前面的描述可以看出,其實被刪的是結點D的後繼結點S。如果被刪結點S為紅色,則紅黑樹性質未被破壞,因此不需做其他調整;如果被刪結點S為黑色,則需進一步做調整處理。

\

圖4 情況3:左右孩子均不為葉子結點

[後繼結點的右孩子SR取代後繼結點S的位置]

綜合情況1、2、3可知,當實際被刪的結點為黑色時,才需進一步做調整處理 —— 實際被刪的結點為紅色時,並不會破壞紅黑樹的5點性質。

2.2 調整過程

當紅黑樹中實際被刪除的結點為黑色時,則可能破壞紅黑樹的5個性質。經過分析總結,破壞紅黑樹性質的情況有如下幾種:

============================================================================

前提1:參照結點N為父結點P的左孩子

============================================================================

情況1):參照結點N的兄弟B是紅色的

處理過程:首先,將父結點P的顏色改為紅色,兄弟結點的顏色改為黑色;其次,以父結點P為支點進行左旋處理 ——情況1轉變為情況2或3、4。如下圖所示:[注意:請注意圖中處理前後node、brother指針的變化,這將是後續處理的參照]

\

圖5 調整情況1

情況2):參照結點N的兄弟B是黑色的,且B的兩個孩子都是黑色的

處理過程:將兄弟結點B的顏色改為紅色 ——情況2處理完成後,不必再進行情況3、4的判斷,重新判斷前提1、2。如下圖所示:[注意:請注意圖中處理前後node、brother指針的變化,這將是後續處理的參照]

\

圖6 調整情況2

情況3):參照結點N的兄弟B是黑色的,且B的左孩子是紅色的,右孩子是黑色的

處理過程:首先,將兄弟結點B的顏色改為紅色,結點B的左孩子改為黑色;其次,以結點B為支點進行右旋處理 ——情況3轉化為情況4,後續必須進行情況4的處理。如下圖所示:[注意:請注意圖中處理前後node、brother指針的變化,這將是後續處理的參照]

\

圖7 調整情況3

情況4):參照結點N的兄弟B是黑色的,且B的左孩子是黑色的,右孩子是紅色的

處理過程:首先,將父結點P的顏色拷貝給兄弟結點B,再將父結點P的顏色改為黑色,兄弟結點的顏色改為黑色;其次,以父結點P為支點,進行左旋處理;再次,將node改為樹的根結點,也意味著調整結束。如下圖所示:[注意:請注意圖中處理前後node指針的變化,這將是後續處理的參照]

\

圖8 調整情況4

[注:藍色表示結點顏色可能為紅,也可能為黑,在此也更能突出復制結點P的顏色給結點B]

============================================================================

前提2:參照結點N為父結點P的右孩子

============================================================================

其處理流程與前提1的四種情況正好相反,後續再補充...

3 編碼實現

如果以下代碼中出現的數據類型、宏、枚舉或函數接口等未在此代碼中定義,則可到《算法導論 之 紅黑樹 - 插入》中找到其具體的定義。
/******************************************************************************
 **函數名稱: rbt_delete
 **功    能: 刪除操作(外部接口)
 **輸入參數: 
 **     tree: 紅黑樹
 **     key: 關鍵字
 **輸出參數: NONE
 **返    回: RBT_SUCCESS:成功  RBT_FAILED:失敗
 **實現描述: 
 **     通過key找到需要被刪除的結點,再調用_rbt_delete()執行刪除操作
 **注意事項: 
 **作    者: # Qifeng.zou # 2013.12.27 #
 ******************************************************************************/
int rbt_delete(rbt_tree_t *tree, int key)
{
    rbt_node_t *node = tree->root;

    if(tree->sentinel == node)
    {
        return RBT_SUCCESS;
    }

    while(tree->sentinel != node)
    {
        if(key == node->key)
        {
            return _rb_delete(tree, node); /* 刪除結點 */
        }
        else if(key < node->key)
        {
            node = node->lchild;
        }
        else
        {
            node = node->rchild;
        }
    }

    return RBT_SUCCESS;
}

代碼1 刪除操作(外部接口)

/******************************************************************************
 **函數名稱: rbt_delete
 **功    能: 刪除結點(內部接口)
 **輸入參數: 
 **     tree: 紅黑樹
 **     dnode: 將被刪除的結點
 **輸出參數: NONE
 **返    回: RBT_SUCCESS:成功  RBT_FAILED:失敗
 **實現描述: 
 **    1. 如果將被刪除的結點dnode無後繼結點,則直接被刪除,並被其左孩子或右孩子替代其位置
 **    2. 如果將被刪除的結點dnode有後繼結點,則將後繼結點的其賦給dnode,並刪除後繼結點,
 **       再將後繼結點的右孩子取代後繼結點的位置
 **    3. 完成1、2的處理之後,如果紅黑樹的性質被破壞,則調用rbt_delete_fixup()進行調整
 **注意事項: 
 **作    者: # Qifeng.zou # 2013.12.28 #
 ******************************************************************************/
int _rb_delete(rbt_tree_t *tree, rbt_node_t *dnode)
{
    rbt_node_t *parent = NULL, *next = NULL, *refer = NULL;

    /* 查找dnode的後繼結點next */
    if((tree->sentinel == dnode->lchild)
        || (tree->sentinel == dnode->rchild))
    {
        next = dnode;
    }
    else
    {
        next = dnode->rchild;
        while(tree->sentinel != next->lchild)
        {
            next = next->lchild;
        }
    }

    /* 設置替代後繼結點的結點refer(參考結點) */
    if(tree->sentinel != next->lchild)
    {
        refer = next->lchild;
    }
    else
    {
        refer = next->rchild;
    }

    refer->parent = next->parent;
    if(tree->sentinel == next->parent)
    {
        tree->root = refer;
    }
    else
    {
        if(next == next->parent->lchild)
        {
            next->parent->lchild = refer;
        }
        else
        {
            next->parent->rchild = refer;
        }
    }

    if(next != dnode)
    {
        dnode->key = next->key;
        /* Copy next's satellite data into dnode */
    }

    if(rbt_is_red(next)) /* Not black */
    {
        free(next);
        return RBT_SUCCESS;
    }

    free(next);

    return rbt_delete_fixup(tree, refer); /* 修復紅黑樹性質 */
}

代碼2 刪除結點(內部接口)

/******************************************************************************
 **函數名稱: rbt_delete_fixup
 **功    能: 修復刪除操作造成的黑紅樹性質的破壞(內部接口)
 **輸入參數: 
 **     tree: 紅黑樹
 **     node: 實際被刪結點的替代結點(注: node有可能是葉子結點)
 **輸出參數: NONE
 **返    回: RBT_SUCCESS:成功  RBT_FAILED:失敗
 **實現描述: 
 **注意事項: 
 **     注意: 被刪結點為黑色結點,才能調用此函數進行性質調整
 **作    者: # Qifeng.zou # 2013.12.28 #
 ******************************************************************************/
int rbt_delete_fixup(rbt_tree_t *tree, rbt_node_t *node)
{
    rbt_node_t *parent = NULL, *brother = NULL;

    while(rbt_is_black(node) && (tree->root != node))
    {   
        /* Set parent and brother */
        parent = node->parent;
        
        if(node == parent->lchild)
        {
            brother = parent->rchild;

            /* Case 1: 兄弟結點為紅色:  以parent為支點, 左旋處理 */
            if(rbt_is_red(brother))
            {
                rbt_set_red(parent);
                rbt_set_black(brother);
                rbt_left_rotate(tree, parent);

                /* 參照結點node不變, 兄弟結點改為parent->rchild */
                brother = parent->rchild;
                
                /* 注意: 此時處理還沒有結束,還需要做後續的調整處理 */
            }

            /* Case 2: 兄弟結點為黑色(默認), 且兄弟結點的2個子結點都為黑色 */
            if(rbt_is_black(brother->lchild) && rbt_is_black(brother->rchild))
            {
                rbt_set_red(brother);
                node = parent;
            }
            else 
            {
                /* Case 3: 兄弟結點為黑色(默認),
                    兄弟節點的左子結點為紅色, 右子結點為黑色:  以brother為支點, 右旋處理 */
                if(rbt_is_black(brother->rchild))
                {
                    rbt_set_black(brother->lchild);
                    rbt_set_red(brother);

                    rbt_right_rotate(tree, brother);

                    /* 參照結點node不變 */
                    brother = parent->rchild;
                }
                
                /* Case 4: 兄弟結點為黑色(默認),
                    兄弟結點右孩子結點為紅色:  以parent為支點, 左旋處理 */
                rbt_copy_color(brother, parent);
                rbt_set_black(brother->rchild);
                rbt_set_black(parent);

                rbt_left_rotate(tree, parent);
                
                node = tree->root;
            }
        }
        else
        {
            brother = parent->lchild;

            /* Case 1: 兄弟結點為紅色:  以parent為支點, 右旋處理 */
            if(rbt_is_red(brother))
            {
                rbt_set_red(parent);
                rbt_set_black(brother);

                rbt_right_rotate(tree, parent);

                /* 參照結點node不變 */
                brother = parent->lchild;
                
                /* 注意: 此時處理還沒有結束,還需要做後續的調整處理 */
            }

            /* Case 2: 兄弟結點為黑色(默認), 且兄弟結點的2個子結點都為黑色 */
            if(rbt_is_black(brother->lchild) && rbt_is_black(brother->rchild))
            {
                rbt_set_red(brother);
                node = parent;
            }
            else
            {
                /* Case 3: 兄弟結點為黑色(默認),
                    兄弟節點的右子結點為紅色, 左子結點為黑色:  以brother為支點, 左旋處理 */
                if(rbt_is_black(brother->lchild))
                {
                    rbt_set_red(brother);
                    rbt_set_black(brother->rchild);

                    rbt_left_rotate(tree, brother);

                    /* 參照結點node不變 */
                    brother = parent->lchild;
                }
            
                /* Case 4: 兄弟結點為黑色(默認), 兄弟結點左孩子結點為紅色: 以parent為支點, 右旋處理 */
                rbt_copy_color(brother, parent);
                rbt_set_black(brother->lchild);
                rbt_set_black(parent);

                rbt_right_rotate(tree, parent);
                
                node = tree->root;
            }
        }
    }

    rbt_set_black(node);
    
    return RBT_SUCCESS;
}

代碼3 刪除調整

處理結果:

首先,隨機生成左圖樹,再隨機的任意個結點後,得到右圖樹,經過分析可以發現:右圖也是一個紅黑樹。經過反復驗證後,可以判斷以上代碼的處理是正確的。


—— 鄒祁峰

2014.01.18 01:21

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved