演算法導論 之 平衡二叉樹 - 刪除 - 遞迴 C語言
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
- 作者:鄒祁峰
- 郵箱:[email protected]
- 部落格:http://blog.csdn.net/qifengzou
- 日期:2013.12.20 12:00
- 轉載請註明來自"
1 前言
在之前的博文《演算法導論 之 平衡二叉樹 - 插入》和《演算法導論 之 平衡二叉樹 - 列印》中已經給出了建立、插入、查詢、列印以及銷燬平衡二叉樹的C語言實現過程,在此篇中出現的一些結構體、巨集、列舉、函式等相關定義可以到以上兩篇中找到。之所以現在才來寫平衡二叉樹的刪除操作,主要是其過程相對比較複雜,且測試和實現過程中出現了各種各樣的問題。
2 處理思路
雖然平衡二叉樹的結點刪除操作的程式碼比較複雜,但是經過認真分析後,可以發現刪除過程只有以下3種情況:[注:只要牢牢把握住以下3點,理解並實現平衡二叉樹的刪除操作不是太難]2.1 被刪的結點是葉子結點
處理思路:
①、將該結點直接從樹中刪除;
②、其父節點的子樹高度的變化將導致父結點平衡因子的變化,通過向上檢索並推算其父結點是否失衡;
③、如果其父結點未失衡,則繼續向上檢索推算其父結點的父結點是否失衡...如此反覆②的判斷,直到根結點;如果向上推算過程中發現了失衡的現象,則進行④的處理;
④、如果其父結點失衡,則判斷是哪種失衡型別[LL、LR、RR、RL],並對其進行相應的平衡化處理。如果平衡化處理結束後,發現與原來以父節點為根結點的樹的高度發生變化,則繼續進行②的檢索推算;如果與原來以父結點為根結點的高度一致時,則可說明父結點的父結點及祖先結點的平衡因子將不會有變化,因此可以退出處理;[注意:刪除操作時的LL和RR型與插入操作的LL和RR型的判斷有一點小區別,那就是刪除操作中,當node->lchild或node->rchild的平衡因子為AVL_EH時,也進行LL或RR操作]
舉例說明:
假設現在要刪除葉子結點D,如圖1所示。首先,將D從樹中刪除,再判斷其父結點C是否失衡,如果失衡則進行平衡化處理;再判斷C的父結點B是否失衡,如果失衡則進行平衡化處理;再判斷B的父結點A是否失衡,如果失衡則....依此類推,直到根結點或高度不再變化為止。
圖1 葉子結點
2.2 被刪的結點只有左子樹或只有右子樹
處理思路:
①、將左子樹(右子樹)替代原有結點C的位置;
②、結點C被刪除後,則以C的父結點B為起始推算點,依此向上檢索推算各結點(父、祖先)是否失衡;
③、如果其父結點未失衡,則繼續向上檢索推算其父結點的父結點是否失衡...如此反覆②的判斷,直到根結點;如果向上推算過程中發現了失衡的現象,則進行④的處理;
④、如果其父結點失衡,則判斷是哪種失衡型別[LL、LR、RR、RL],並對其進行相應的平衡化處理。如果平衡化處理結束後,發現與原來以父節點為根結點的樹的高度發生變化,則繼續進行②的檢索推算;如果與原來以父結點為根結點的高度一致時,則可說明父結點的父結點及祖先結點的平衡因子將不會有變化,因此可以退出處理;[注意:刪除操作時的LL和RR型與插入操作的LL和RR型的判斷有一點小區別,那就是刪除操作中,當node->lchild或node->rchild的平衡因子為AVL_EH時,也進行LL或RR操作]
圖2 只有左子樹 或 只有右子樹
2.3 被刪的結點既有左子樹又有右子樹
處理思路:
①、找到被刪結點C和替代結點R(結點C的前繼結點或後繼結點 —— 在此選擇前繼);
②、將替代結點R的值賦給結點C,再把替代結點R的左孩子RL替換替代結點R的位置,最後把替代結點R的空間釋放掉;[注意:最終刪除的是替代結點R,而之前要求被刪除的結點C並未被刪除 - 原因:通過修改結點C的值達到結點C和結點R的替換]
③、替代結點R被刪除後,則以R的父結點E為起始推算點,依此向上檢索推算父結點或祖先結點是否失衡;
④、如果其父結點未失衡,則繼續向上檢索推算其父結點的父結點是否失衡...如此反覆③的判斷,直到根結點;如果向上推算過程中發現了失衡的現象,則進行⑤的處理;
⑤、如果其父結點失衡,則判斷是哪種失衡型別[LL、LR、RR、RL],並對其進行相應的平衡化處理。如果平衡化處理結束後,發現與原來以父節點為根結點的樹的高度發生變化,則繼續進行②的檢索推算;如果與原來以父結點為根結點的高度一致時,則可說明父結點的父結點及祖先結點的平衡因子將不會有變化,因此可以退出處理;[注意:刪除操作時的LL和RR型與插入操作的LL和RR型的判斷有一點小區別,那就是刪除操作中,當node->lchild或node->rchild的平衡因子為AVL_EH時,也進行LL或RR操作]
圖3 既有左子樹 又有右子樹
3 程式碼實現
/****************************************************************************** **函式名稱: avl_delete **功 能: 刪除key值結點(對外介面) **輸入引數: ** tree: 平衡二叉樹 ** key: 被刪除的關鍵字 **輸出引數: NONE **返 回: AVL_SUCCESS:成功 AVL_FAILED:失敗 **實現描述: **注意事項: **作 者: # Qifeng.zou # 2013.12.19 # ******************************************************************************/int avl_delete(avl_tree_t *tree, int key){ bool lower = false; /* 記錄高度是否降低 */ if (NULL == tree->root) { return AVL_SUCCESS; } return _avl_delete(tree, tree->root, key, &lower);}
程式碼1 刪除結點(外部介面)
/****************************************************************************** **函式名稱: _avl_delete **功 能: 在以node為根結點的子樹中刪除指定的key值結點(內部介面) **輸入引數: ** tree: 平衡二叉樹 ** node: 以node為根結點的子樹 ** key: 被刪除的關鍵字 **輸出引數: ** lower: 高度是否降低 **返 回: AVL_SUCCESS:成功 AVL_FAILED:失敗 **實現描述: **注意事項: **作 者: # Qifeng.zou # 2013.12.19 # ******************************************************************************/int _avl_delete(avl_tree_t *tree, avl_node_t *node, int key, bool *lower){ avl_node_t *parent = node->parent; /* 1. 查詢需要被刪除的結點 */ if (key < node->key) { /* 左子樹上查詢 */ if (NULL == node->lchild) { return AVL_SUCCESS; } _avl_delete(tree, node->lchild, key, lower); if (true == *lower) { return avl_delete_left_balance(tree, node, lower); } return AVL_SUCCESS; } else if (key > node->key) { /* 右子樹上查詢 */ if (NULL == node->rchild) { return AVL_SUCCESS; } _avl_delete(tree, node->rchild, key, lower); if (true == *lower) { return avl_delete_right_balance(tree, node, lower); } return AVL_SUCCESS; } /* 2. 已找到將被刪除的結點node */ /* 2.1 右子樹為空, 只需接它的左子樹(葉子結點也走這) */ if (NULL == node->rchild) { *lower = true; avl_instead_child(tree, parent, node, node->lchild); free(node), node = NULL; return AVL_SUCCESS; } /* 2.2 左子樹空, 只需接它的右子樹 */ else if (NULL == node->lchild) { *lower = true; avl_instead_child(tree, parent, node, node->rchild) free(node), node=NULL; return AVL_SUCCESS; } /* 2.3 左右子樹均不為空: 查詢左子樹最右邊的結點 替換被刪的結點 */ avl_instead_and_delete(tree, node, node->lchild, lower); if (true == *lower) { return avl_delete_left_balance(tree, node, lower); } return AVL_SUCCESS;}
程式碼2 查詢並刪除結點(內部介面)
/****************************************************************************** **函式名稱: avl_instead_and_delete **功 能: 找到替換結點, 並替換被刪除的結點(內部介面) **輸入引數: ** tree: 平衡二叉樹 ** node: 將被刪除的結點 ** prev: 前繼結點:此結點最右端的結點將會用來替換被刪除的結點. **輸出引數: ** lower: 高度是否變化 **返 回: AVL_SUCCESS:成功 AVL_FAILED:失敗 **實現描述: **注意事項: ** 注:在此其實並不會刪除node, 而是將prev的值給了node, 再刪了prev. ** 因為在此使用的遞迴演算法, 如果真把node給釋放了,會造成壓棧的資訊出現錯誤! **作 者: # Qifeng.zou # 2013.12.19 # ******************************************************************************/int avl_instead_and_delete(avl_tree_t *tree, avl_node_t *node, avl_node_t *prev, bool *lower){ if (NULL == instead->rchild) { *lower = true; node->key = prev->key; /* 注: 將prev的值給了node */ if (prev == node->lchild) { avl_set_lchild(node, prev->lchild); /* prev->parent == node結點可能失衡,此處理交給前棧的函式處理 */ } else { avl_set_rchild(prev->parent, prev->lchild); /* prev的父結點可能失衡,此處理交給前棧的函式處理 */ } free(prev), prev=NULL; /* 注意: 釋放的不是node, 而是釋放prev */ return AVL_SUCCESS; } avl_instead_and_delete(tree, node, prev->rchild, lower); if (true == *lower) { /* node的父結點可能失衡,此處理交給前棧的函式處理 但prev可能失衡,因此必須在此自己處理 */ avl_delete_right_balance(tree, prev, lower); } return AVL_SUCCESS;}
程式碼3 替換並刪除結點(內部介面)
/****************************************************************************** **函式名稱: avl_delete_left_balance **功 能: 結點node的左子樹某結點被刪除, 左高度降低後, 平衡化處理(內部介面) **輸入引數: ** tree: 平衡二叉樹 ** node: 結點node的左子樹的某個結點已被刪除 **輸出引數: ** lower: 高度是否變化 **返 回: AVL_SUCCESS:成功 AVL_FAILED:失敗 **實現描述: **注意事項: **作 者: # Qifeng.zou # 2013.12.19 # ******************************************************************************/int avl_delete_left_balance(avl_tree_t *tree, avl_node_t *node, bool *lower){ avl_node_t *rchild = NULL, *rlchild = NULL, *parent = node->parent; switch (node->bf) { case AVL_LH: /* 左高: 左子樹高度減1 樹變矮 */ { node->bf = AVL_EH; *lower = true; break; } case AVL_EH: /* 等高: 左子樹高度減1 樹高度不變 */ { node->bf = AVL_RH; *lower = false; break; } case AVL_RH: /* 右高: 左子樹高度減1 樹失去平衡 */ { rchild = node->rchild; switch (rchild->bf) { case AVL_EH: /* RR型: 向左旋轉 - 區別:插入操作時對AVL_EH不做處理 */ case AVL_RH: /* RR型: 向左旋轉 */ { if (AVL_EH == rchild->bf) { *lower = false; rchild->bf = LH; node->bf = AVL_RH; } else { /* AVL_RH == rchild->bf */ *lower = true; rchild->bf = AVL_EH; node->bf = AVL_EH; } avl_set_rchild(node, rchild->lchild); avl_set_lchild(rchild, node); avl_instead_child(tree, parent, node, rchild); break; } case AVL_LH: /* RL型: 先向右旋轉 再向左旋轉 */ { *lower = true; rlchild = rchild->lchild; switch (rlchild->bf) { case AVL_LH: { node->bf = AVL_EH; rchild->bf = AVL_RH; rlchild->bf = AVL_EH; break; } case AVL_EH: { node->bf = AVL_EH; rchild->bf = AVL_EH; rlchild->bf = AVL_EH; break; } case AVL_RH: { node->bf = AVL_LH; rchild->bf = AVL_EH; rlchild->bf = AVL_EH; break; } } avl_set_rchild(node, rlchild->lchild); avl_set_lchild(rchild, rlchild->rchild); avl_set_lchild(rlchild, node); avl_set_rchild(rlchild, rchild); avl_instead_child(tree, parent, node, rlchild); break; } } break; } } return AVL_SUCCESS;}
程式碼4 左子樹高度降低後平衡化處理
/****************************************************************************** **函式名稱: avl_delete_right_balance **功 能: 結點node的右子樹某結點被刪除, 左高度降低後, 平衡化處理(內部介面) **輸入引數: ** tree: 平衡二叉樹 ** node: 結點node的右子樹的某個結點已被刪除 **輸出引數: ** lower: 高度是否變化 **返 回: AVL_SUCCESS:成功 AVL_FAILED:失敗 **實現描述: **注意事項: **作 者: # Qifeng.zou # 2013.12.19 # ******************************************************************************/int avl_delete_right_balance(avl_tree_t *tree, avl_node_t *node, bool *lower){ avl_node_t *lchild = NULL, *lrchild = NULL, *parent = node->parent; switch (node->bf) { case AVL_LH: /* 左高: 右子樹高度減1 樹失去平衡 */ { lchild = node->lchild; switch (lchild->bf) { case AVL_EH: /* LL型: 向右旋轉 - 區別:插入操作時,對AVL_EH不做處理 */ case AVL_LH: /* LL型: 向右旋轉 */ { if (AVL_EH == lchild->bf) { *lower = false; lchild->bf = AVL_RH; node->bf = AVL_LH; } else { /* AVL_LH == lchild->bf */ *lower = true; lchild->bf = AVL_EH; node->bf = AVL_EH; } avl_set_lchild(node, lchild->rchild); avl_set_rchild(lchild, node); avl_instead_child(tree, parent, node, lchild); break; } case AVL_RH: /* LR型: 先向左旋轉 再向右旋轉 */ { *lower = true; lrchild = lchild->rchild; switch (lrchild->bf) { case AVL_LH: { node->bf = AVL_RH; lchild->bf = AVL_EH; lrchild->bf = AVL_EH; break; } case AVL_EH: { node->bf = AVL_EH; lchild->bf = AVL_EH; lrchild->bf = AVL_EH; break; } case AVL_RH: { node->bf = AVL_EH; lchild->bf = AVL_LH; lrchild->bf = AVL_EH; break; } } avl_set_lchild(node, lrchild->rchild); avl_set_rchild(lchild, lrchild->lchild); avl_set_rchild(lrchild, node); avl_set_lchild(lrchild, lchild); avl_instead_child(tree, parent, node, lrchild); break; } } break; } case AVL_EH: /* 等高: 右子樹高度減1 樹高度不變 */ { node->bf = AVL_LH; *lower = false; break; } case AVL_RH: /* 右高: 右子樹高度減1 樹變矮 */ { node->bf = AVL_EH; *lower = true; break; } } return AVL_SUCCESS;}
程式碼5 右子樹高度降低後 平衡化處理
/****************************************************************************** **函式名稱: avl_assert **功 能: 檢測結點是否正常 **輸入引數: ** node: 被檢測的結點 **輸出引數: NONE **返 回: VOID **實現描述: **注意事項: ** 注:在增加或刪除結點的過程中,會有很多指標的操作,稍不謹慎,可能就會出現嚴重問題。 ** 隨著插入或刪除的過程,樹的結構不斷調整和變化,要想通過肉眼和GDB檢視樹內部結構 ** 是否正常,這將變得越來越困難。但是通過呼叫此函式,可快速判斷出當前結點的相關 ** 指標資訊是否正常,一旦出現異常,則會直接coredump,再通過GDB便可快速的找出 ** 錯誤程式碼。 **作 者: # Qifeng.zou # 2013.12.20 # ******************************************************************************/void avl_assert(const avl_node_t *node){ if((NULL == node) || (NULL == node->parent)) { return; } if((node->parent->lchild != node) /* 檢查父子關係是否正常 */ && (node->parent->rchild != node)) { assert(0); } if((node->parent == node->lchild) /* 檢查是否存在環行連結串列 */ || (node->parent == node->rchild)) { assert(0); }}
程式碼6 結點檢測4 結果展示
左圖為原始平衡二叉樹,隨機刪除多個結點後,得到右圖。通過觀察可知右圖依然是一棵平衡二叉樹。圖4 測試結果