1. 程式人生 > >演算法導論 之 平衡二叉樹 - 刪除 - 遞迴 C語言

演算法導論 之 平衡二叉樹 - 刪除 - 遞迴 C語言

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               


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 測試結果



           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述