1. 程式人生 > >演算法導論 之 紅黑樹 - 刪除 C語言

演算法導論 之 紅黑樹 - 刪除 C語言

               


1 引言

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

首先我們再回顧一下紅黑樹的5個性質:

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

    ②、根結點是黑色的;

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

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

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

   和插入操作一樣,結點的刪除操作的時間複雜度也是O([email protected][注:以2為底數,N為對數]

,但刪除操作的處理更復雜一些。


2 刪除處理

2.1 外部介面

    呼叫介面刪除指定key結點時,其內部首先會查詢紅黑樹中是否存在key結點。如果key結點不存在,則無需進行任何的處理;如果key結點存在,則呼叫_rbt_delete()刪除結點。     紅黑樹是查詢樹的一種,其查詢key結點的過程與查詢樹的查詢過程極其相似。故,外部介面的實現程式碼如下:[注:程式碼中出現的資料型別、巨集、列舉或函式定義可以參考演算法導論 之 紅黑樹 - 插入]
/****************************************************************************** **函式名稱: rbt_delete **功    能: 刪除結點(外部介面) **輸入引數:  **     tree: 紅黑樹 **     key: 關鍵字 **輸出引數: NONE **返    回: RBT_SUCCESS:成功  RBT_FAILED:失敗 **實現描述:  **    1. 如果key的結點不存在,則無需進行任何的處理; **    2. 如果key的結點存在,則呼叫_rbt_delete()刪除結點。 **注意事項:  **作    者: # Qifeng.zou # 2013.12.27 # ******************************************************************************/
int rbt_delete(rbt_tree_t *tree, int key){    rbt_node_t *node = tree->root;    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 刪除操作

2.2 刪除過程

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

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

    處理過程:

        ①、刪除結點D,並用右孩子結點替代結點D的位置;

        ②、如果被刪結點D為紅色,則紅黑樹性質未被破壞,因此無需做其他調整;

        ③、如果被刪結點D為黑色,則需進一步做調整處理。


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

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



圖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點性質,其實現的過程如下:[注:程式碼中出現的資料型別、巨集、列舉或函式定義可以參考演算法導論 之 紅黑樹 - 插入]

/****************************************************************************** **函式名稱: _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;    /* Case 1: 被刪結點D的左孩子為葉子結點, 右孩子無限制(可為葉子結點,也可為非葉子結點) */    if(tree->sentinel == dnode->lchild) {        parent = dnode->parent;        refer = dnode->rchild;        refer->parent = parent;        if(tree->sentinel == parent) {            tree->root = refer;        } else if(dnode == parent->lchild) {            parent->lchild = refer;        } else { /* dnode == parent->rchild */            parent->rchild = refer;        }        if(rbt_is_red(dnode)) {            free(dnode);            return RBT_SUCCESS;        }        free(dnode);        return rbt_delete_fixup(tree, refer);    }    /* Case 2: 被刪結點D的右孩子為葉子結點, 左孩子不為葉子結點 */    else if(tree->sentinel == dnode->rchild) {        parent = dnode->parent;        refer = dnode->lchild;        refer->parent = parent;        if(tree->sentinel == parent) {            tree->root = refer;        } else if(dnode == parent->lchild) {            parent->lchild = refer;        } else { /* dnode == parent->rchild */            parent->rchild = refer;        }        if(rbt_is_red(dnode)) {            free(dnode);            return RBT_SUCCESS;        }        free(dnode);        return rbt_delete_fixup(tree, refer);    }    /* Case 3: 被刪結點D的左右孩子均不為葉子節點 */    /* 查詢dnode的後繼結點next */    next = dnode->rchild;    while(tree->sentinel != next->lchild) {        next = next->lchild;    }    parent = next->parent;    refer = next->rchild;    refer->parent = parent;    if(next == parent->lchild) {        parent->lchild = refer;    } else { /* next == parent->rchild */        parent->rchild = refer;    }    dnode->key = next->key;    if(rbt_is_red(next)) { /* Not black */        free(next);        return RBT_SUCCESS;    }    free(next);    return rbt_delete_fixup(tree, refer);}
程式碼 2 刪除結點

2.3 調整過程

    當紅黑樹中實際被刪除的結點為黑色時,則可能破壞紅黑樹的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和兄弟結點的右孩子BR的顏色改為黑色;

        ②、以父結點P為支點,進行左旋處理;

        ③、將node改為樹的根結點,也意味著調整結束。

    如下圖所示:[注意:請注意圖中處理前後node指標的變化,這將是後續處理的參照]


圖8 調整情況4

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

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

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

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

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

    處理過程:

        ①、將父結點P的顏色改為紅色,兄弟結點的顏色改為黑色;

        ②、以父結點P為支點進行右旋處理;

        ③、情況5轉變為情況6或7、8,後續需要依次判斷處理。

    如下圖所示:[注意:請注意圖中處理前後node、brother指標的變化,這將是後續處理的參照]


圖9 調整情況5

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

    處理過程:

        ①、將兄弟結點B的顏色改為紅色;

        ②、情況6處理完成後,不必再進行情況7、8的判斷,但需要重新迴圈判斷前提1、2。

    如下圖所示:[注意:請注意圖中處理前後node、brother指標的變化,這將是後續處理的參照]


圖10 調整情況6

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

    處理過程:

        ①、將兄弟結點B的顏色改為紅色,結點B的右孩子改為黑色;

        ②、以結點B為支點進行左旋處理;

        ③、情況7轉化為情況8,後續必須進行情況8的處理

    如下圖所示:[注意:請注意圖中處理前後node、brother指標的變化,這將是後續處理的參照]


圖11 調整情況7

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

    處理過程:

        ①、將父結點P的顏色拷貝給兄弟結點B,再將父結點P和兄弟結點的左結點BL顏色改為黑色;

        ②、以父結點P為支點,進行右旋處理;

        ③、將node改為樹的根結點,也意味著調整結束。

    如下圖所示:[注意:請注意圖中處理前後node指標的變化,這將是後續處理的參照]


圖12 調整情況8

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

    綜合以上情況的分析,刪除結點後的調整過程的實現程式碼如下所示:[注:程式碼中出現的資料型別、巨集、列舉或函式定義可以參考演算法導論 之 紅黑樹 - 插入]

/****************************************************************************** **函式名稱: 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;                /* 前提1: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;            }        }        /* 前提2:node為parent的右孩子 */        else {            brother = parent->lchild;            /* Case 5: 兄弟結點為紅色:  以parent為支點, 右旋處理 */            if(rbt_is_red(brother)) {                rbt_set_red(parent);                rbt_set_black(brother);                rbt_right_rotate(tree, parent);                /* 參照結點node不變 */                brother = parent->lchild;                                /* 注意: 此時處理還沒有結束,還需要做後續的調整處理 */            }            /* Case 6: 兄弟結點為黑色(預設), 且兄弟結點的2個子結點都為黑色 */            if(rbt_is_black(brother->lchild) && rbt_is_black(brother->rchild)) {                rbt_set_red(brother);                node = parent;            } else {                /* Case 7: 兄弟結點為黑色(預設),                    兄弟節點的右子結點為紅色, 左子結點為黑色:  以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 8: 兄弟結點為黑色(預設), 兄弟結點左孩子結點為紅色: 以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 刪除調整


3 處理結果

    首先,隨機輸入多個key生成左圖樹,再隨機刪除任意key後,得到右圖樹。經過分析可以發現:右圖也是一個紅黑樹。經過反覆驗證後,可以判斷以上程式碼的處理是正確的。[注:紅黑樹的列印可以參考博文《演算法導論 之 紅黑樹 - 列印、銷燬]


圖13 結果展示


           

再分享一下我老師大神的人工智慧教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智慧的隊伍中來!https://blog.csdn.net/jiangjunshow