演算法導論 之 紅黑樹 - 刪除 C語言
- 作者:鄒祁峰
- 郵箱:[email protected]
- 部落格:http://blog.csdn.net/qifengzou
- 日期:2014.01.18 01:21
- 轉載請註明來自"祁峰"的CSDN部落格
1 引言
在《演算法導論 之 紅黑樹 - 插入》中已經對紅黑樹的5個性質做了較詳細的分析,同時也給出了insert操作的C語言實現。
①、每個節點要麼是紅色的,要麼是黑色的;
②、根結點是黑色的;
③、所有葉子結點(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