演算法導論 之 平衡二叉樹 - 建立 插入 查詢 銷燬 - 遞迴 C語言
阿新 • • 發佈:2018-11-15
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
- 作者:鄒祁峰
- 郵箱:[email protected]
- 部落格:http://blog.csdn.net/qifengzou
- 日期:2013.12.13 17:00
- 轉載請註明來自"
1 引言
在構造二叉排序樹過程中,即使輸入相同的關鍵字組合,但關鍵字順序不一致時,產生的也不是不同形態的二叉排序樹,其插入、查詢、刪除的效能差別很大(圖1所示),如: ①、當組成的二叉排序樹的形態為單分支樹時,其平均查詢時間為(N+1)/2,最差查詢時間為N. ②、當組成的二叉排序樹的形態為平衡二叉樹時,其插入、刪除、平均查詢、最差查詢時間均為[email protected][以2為底數,以N為對數].大家可以通過以下圖形對時間變化的趨勢有一個大概的印象:
圖1 變化趨勢對比圖
由圖的變化趨勢可知,當N逐漸增大時,時間相差的倍數越來越大[如:當N=2^32時,y = N/2 = 2^31,而y = [email protected] = 2^5, 其效能差異可想而知]。因此,為了提高對二叉排序樹的操作效能,很有必要在構造二叉樹排序樹時,進行平衡化處理,將其調整為一棵平衡二叉樹。
2 平衡過程
平衡二叉樹[Balanced Binary Tree]又稱為AVL樹,是二叉排序樹的一種形式,其特點是:樹中每個結點的左、右子樹的深度之差的絕對值不超過1,即:|Hl - Hr| <= 1。
結點的平衡因子[Balance factor]:該結點的左子樹深度Hl減去該結點的右子樹的深度Hr。平衡二叉樹所有結點的平衡因子的值只能為-1,0,1。
在構建平衡二叉樹的過程中,插入一個新結點後,可能會造成平衡二叉樹失去平衡。失去平衡後進行調整的規律可歸納為以下4種情況:[注:以下操作是平衡處理的核心,請認真分析總結]
2.1 LL型
當結點A的平衡因子為2(失衡),且其左子結點B的平衡因子為1時,則可判定為LL型失衡! 失衡場景: 新結點x插在左重結點A[A是離插入新結點x位置最近的左重結點]的左孩子的左分支上,造成結點A失衡,如下圖所示:[注:AR表示結點A的右子樹,BL表示結點B的左子樹,BR表示結點B的右子樹]圖2 LL型 平衡過程:(如圖3所示) ①、BA向右旋轉90度:結點B替換結點A的位置 ②、結點B的右孩子BR改為結點A的左孩子,把結點A作為結點B的右孩子
圖3 LL型平衡結果
2.2 LR型
當結點A的平衡因子為2,且其左孩子結點B的平衡因子為-1時,則可判斷為LR型 - 但C的平衡因子有2種情況:-1, 1。 失衡場景:結點C的平衡因子為1時 新結點x插在左重結點A[A是離插入新結點x位置最近的左重結點]的左孩子的右孩子的左分支上,如下圖所示:圖4 LR型 平衡過程:(如圖5、6所示) ①、將CB向左旋轉90度,把C的左孩子CL作為B的右孩子,再將B作為C的左孩子,C替代B的位置
圖5 LR型平衡-左旋
②、將BCA向右旋轉90度,把C的右孩子CR作為A的左孩子,將A作為C的右孩子,C替代A的位置
圖6 LR型平衡-右旋
2.3 RR型
當結點A的平衡因子為-2,且其左子結點B的平衡因子為-1時,則可判斷為RR型。 失衡描述: 新結點x插在右重結點A[A是離插入新結點x位置最近的右重結點]的右孩子的右分支上,如下圖所示:圖7 RR型 平衡過程:(如圖3所示) ①、AB向左旋轉90度:結點B替換結點A的位置 ②、把B的左孩子BR改為A的右孩子,把A作為B的左孩子
圖8 RR型平衡結果-左旋
2.4 RL型
當結點A的平衡因子為-2,且其右孩子結點B的平衡因子為1時,則可判斷為RL型 - 但C的平衡因子有2種情況:-1, 1。失衡描述①:結點C的平衡因子為-1時 新結點x插在左重結點A[A是離插入新結點x位置最近的右重結點]的右孩子的左孩子的右分支上,如下圖所示:
圖9 RL型 平衡過程:(如圖10、11所示) ①、將CB向右旋轉90度:結點C替代結點B的位置,再把結點C的右孩子CR作為B的左孩子,再將B作為C的右孩子
圖10 RL型平衡-右旋
②、將BCA向左旋轉90度:結點C替代結點A的位置,把C的左孩子CL作為A的右孩子,將A作為C的左孩子
圖11 RL型平衡結果-左旋
3 操作介面
3.1 結構定義
->1 結點結構定義/* 結點結構 */typedef struct _avl_node_t{ struct _avl_node_t *parent; /* 父結點 */ struct _avl_node_t *lchild; /* 左孩子 */ struct _avl_node_t *rchild; /* 右孩子 */ int key; /* 結點值: 如果想構造成通用的平衡二叉樹,在此可使用void *型別 */ int bf; /* 平衡因子 */}avl_node_t;
程式碼1 結點結構
->2 樹結構定義
/* 樹結構 */typedef struct{ node_t *root; /* 根結點 */ /* 如果想構造成通用的平衡二叉樹,可以在此增加一個比較函式指標,其型別為: typedef int (*cmp)(const void *s1, const void *s2) 1. 當s1 < s2時: 返回值小於0 2. 當s1 == s2時: 返回值等於0 3. 當s1 > s2時: 返回值大於0 */}avl_tree_t;
程式碼2 平衡二叉樹結構
->3 錯誤碼:可根據實際情況動態擴充套件
typedef enum{ AVL_SUCCESS /* 成功 */ , AVL_FAILED = ~0x7FFFFFFF /* 失敗 */ , AVL_NODE_EXIST /* 結點存在 */ , AVL_ERR_STACK /* 棧異常 */}AVL_RET_e;
程式碼3 返回值定義
->4 其他定義:
/* 平衡因子 */#define AVL_RH (-1) /* 右高 */#deifne AVL_EH (0) /* 等高 */#define AVL_LH (1) /* 左高 */#define AVL_MAX_DEPTH (512) /* AVL棧的最大深度 *//* BOOL型別 */typedef int bool;#define true (1)#define false (0)/* 設定node的左孩子結點 */#define avl_set_lchild(node, lc) \{ \ (node)->lchild = (lc); \ if (NULL != (lc)) { \ (lc)->parent = (node); \ } \}/* 設定node的右孩子結點 */#define avl_set_rchild(node, rc) \{ \ (node)->rchild = (rc); \ if (NULL != (rc)) { \ (rc)->parent = (node); \ } \}/* 替換父結點的孩子結點 */#define avl_instead_child(tree, parent, old, _new) \{ \ if (NULL == parent) { \ (tree)->root = (_new); \ if (NULL != (_new)) { \ (_new)->parent = NULL; \ } \ } \ else if (parent->lchild == old) { \ avl_set_lchild(parent, _new); \ } \ else if (parent->rchild == old) { \ avl_set_rchild(parent, _new); \ } \}
程式碼4 其他定義
3.2 建立物件
增加建立平衡二叉樹物件的介面可以有效的遮蔽平衡二叉樹的成員變數的定義,使用者不必關心哪些引數需要設定或修改,只需知道引數介面便可正確使用。其程式碼實現如下:/****************************************************************************** **函式名稱: avl_creat **功 能: 建立平衡二叉樹物件(對外介面) **輸入引數: ** tree: 平衡二叉樹 **輸出引數: NONE **返 回: AVL_SUCCESS:成功 AVL_FAILED:失敗 **實現描述: **注意事項: **作 者: # Qifeng.zou # 2013.12.19 # ******************************************************************************/int avl_creat(avl_tree_t **tree){ *tree = (avl_tree_t *)calloc(1, sizeof(avl_tree_t)); if (NULL == *tree) { return AVL_FAILED; } (*tree)->root = NULL; return AVL_SUCCESS;}
程式碼5 建立平衡二叉樹
3.3 插入結點
花了整整一天的時間進行的程式碼的編寫和除錯,終於完成了使用C語言實現平衡二叉樹的插入處理,在測試過程中,竟然發現構造的二叉樹形成了死迴圈,使用GDB怎麼都跟不出來,最終還是通過assert(node == node->rchild->parent)找出了錯誤的程式碼!其程式碼如下所示:[可直接編譯執行]/****************************************************************************** **函式名稱: avl_insert **功 能: 插入新結點(對外介面) **輸入引數: ** tree: 平衡二叉樹 ** node: 新結點 **輸出引數: NONE **返 回: AVL_SUCCESS:成功 AVL_NODE_EXIST:已存在 AVL_FAILED:失敗 **實現描述: ** 1. 當樹的根結點為空時,則直接建立根結點,並賦值 ** 2. 當樹的根結點不為空時,則呼叫_avl_insert()進行處理 **注意事項: **作 者: # Qifeng.zou # 2013.12.13 # ******************************************************************************/int avl_insert(avl_tree_t *tree, int key){ bool taller = false; avl_node_t *node = tree->root; /* 如果為空樹,則建立第一個結點 */ if (NULL == node) { node = (node_t *)calloc(1, sizeof(node_t)); if (NULL == node) { return AVL_FAILED; } node->parent = NULL; node->rchild = NULL; node->lchild = NULL; node->bf = AVL_EH; node->key = key; tree->root = node; return AVL_SUCCESS; } return _avl_insert(tree, node, key, &taller); /* 呼叫插入結點的介面 */}
程式碼6 插入結點(對外介面)
/****************************************************************************** **函式名稱: _avl_insert **功 能: 插入新結點(內部介面) **輸入引數: ** tree: 平衡二叉樹 ** node: 需在該結點的子樹上插入key ** key: 需被插入的key **輸出引數: ** taller: 是否增高 **返 回: AVL_SUCCESS:成功 AVL_NODE_EXIST:已存在 AVL_FAILED:失敗 **實現描述: ** 1. 當結點關鍵字等於key值時,結點已存在 ** 2. 當結點關鍵字小於key值時,插入右子樹 ** 3. 當結點關鍵字大於key值時,插入左子樹 **注意事項: **作 者: # Qifeng.zou # 2013.12.13 # ******************************************************************************/static int _avl_insert(avl_tree_t *tree, avl_node_t *node, int key, bool *taller){ if (key == node->key) { /* 結點已存在 */ *taller = false; return AVL_NODE_EXIST; } else if (key > node->key) { /* 插入右子樹 */ return avl_insert_right(tree, node, key, taller); } /* 插入左子樹 */ return avl_insert_left(tree, node, key, taller);}
程式碼7 插入結點(內部介面)
/****************************************************************************** **函式名稱: avl_insert_right **功 能: 在node的右子樹中插入新結點(內部介面) **輸入引數: ** tree: 平衡二叉樹 ** node: 需在該結點的子樹上插入key ** key: 需被插入的key **輸出引數: ** taller: 是否增高 **返 回: AVL_SUCCESS:成功 AVL_NODE_EXIST:已存在 AVL_FAILED:失敗 **實現描述: **注意事項: **作 者: # Qifeng.zou # 2013.12.13 # ******************************************************************************/static int avl_insert_right(avl_tree_t *tree, avl_node_t *node, int key, bool *taller){ int ret = -1; avl_node_t *add = NULL; if (NULL == node->rchild) { add = (node_t *)calloc(1, sizeof(node_t)); if (NULL == add) { *taller = false; return AVL_FAILED; } add->lchild = NULL; add->rchild = NULL; add->parent = node; add->key = key; add->bf = AVL_EH; node->rchild = add; *taller = true; /* node的高度增加了 */ } else { ret = _avl_insert(tree, node->rchild, key, taller); if ((AVL_SUCCESS != ret)) { return ret; } } if (false == *taller) { return AVL_SUCCESS; } /* 右增高: 進行平衡化處理 */ switch (node->bf) { case AVL_LH: /* 左高: 右子樹增高 不會導致失衡 */ { node->bf = AVL_EH; *taller = false; return AVL_SUCCESS; } case AVL_EH: /* 等高: 右子樹增高 不會導致失衡 */ { node->bf = AVL_RH; *taller = true; return AVL_SUCCESS; } case AVL_RH: /* 右高: 右子樹增高 導致失衡 */ { avl_right_balance(tree, node); *taller = false; return AVL_SUCCESS; } } return AVL_FAILED;}
程式碼8 插入右子樹(內部介面)
/****************************************************************************** **函式名稱: avl_insert_left **功 能: 在node的左子樹中插入新結點(內部介面) **輸入引數: ** tree: 平衡二叉樹 ** node: 需在該結點的子樹上插入key ** key: 需被插入的key **輸出引數: ** taller: 是否增高 **返 回: AVL_SUCCESS:成功 AVL_NODE_EXIST:已存在 AVL_FAILED:失敗 **實現描述: **注意事項: **作 者: # Qifeng.zou # 2013.12.13 # ******************************************************************************/static int avl_insert_left(avl_tree_t *tree, avl_node_t *node, int key, bool *taller){ int ret = -1; avl_node_t *add = NULL; if (NULL == node->lchild) { add = (node_t *)calloc(1, sizeof(node_t)); if (NULL == add) { *taller = false; return AVL_FAILED; } add->lchild = NULL; add->rchild = NULL; add->parent = node; add->key = key; add->bf = AVL_EH; node->lchild = add; *taller = true; /* node的高度增加了 */ } else { ret = _avl_insert(tree, node->lchild, key, taller); if (AVL_SUCCESS != ret) { return ret; } } if (false == *taller) { return AVL_SUCCESS; } /* 左增高: 進行平衡化處理 */ switch(node->bf) { case AVL_RH: /* 右高: 左子樹增高 不會導致失衡 */ { node->bf = AVL_EH; *taller = false; return AVL_SUCCESS; } case AVL_EH: /* 等高: 左子樹增高 不會導致失衡 */ { node->bf = AVL_LH; *taller = true; return AVL_SUCCESS; } case AVL_LH: /* 左高: 左子樹增高 導致失衡 */ { avl_left_balance(tree, node); *taller = false; return AVL_SUCCESS; } } return AVL_FAILED;}
程式碼9 插入左子樹(內部介面)
/****************************************************************************** **函式名稱: avl_rr_balance **功 能: RR型平衡化處理 - 向左旋轉(內部介面) **輸入引數: ** tree: 平衡二叉樹 ** node: 右邊失去平衡的結點 **輸出引數: NONE **返 回: AVL_SUCCESS:成功 AVL_FAILED:失敗 **實現描述: RR型 ** A C ** / \ / \ ** AL C -> A CR ** / \ / \ \ ** CL CR AL CL X ** \ ** X ** (1) (2) ** 說明: 結點A是失衡結點,此時當結點C的平衡因子為-1時,可判斷為RR型。 **注意事項: ** 1. 圖(1)中A表示右邊失衡的結點 圖(2)表示平衡處理的結果 **作 者: # Qifeng.zou # 2013.12.13 # ******************************************************************************/static int avl_rr_balance(avl_tree_t *tree, avl_node_t *node){ avl_node_t *rchild = node->rchild, *parent = node->parent; avl_set_rchild(node, rchild->lchild); node->bf = AVL_EH; avl_set_lchild(rchild, node); rchild->bf = AVL_EH; avl_instead_child(tree, parent, node, rchild); return AVL_SUCCESS;}
程式碼10 RR型平衡化處理(內部介面)
/****************************************************************************** **函式名稱: avl_rl_balance **功 能: RL型平衡化處理 - 先向右旋轉 再向左旋轉(內部介面) **輸入引數: ** tree: 平衡二叉樹 ** node: 右邊失去平衡的結點 **輸出引數: NONE **返 回: AVL_SUCCESS:成功 AVL_FAILED:失敗 **實現描述: ** 場景1: RL型 ** A B ** / \ / \ ** AL C -> A C ** / \ / \ / \ ** B CR AL BL BR CR ** / \ ** BL BR ** (1) (2) ** 說明: 結點A是失衡結點,此時當結點C的平衡因子為1時,可判斷為RL型。 ** 雖然此時結點B的平衡因子的值可能為:-1, 0, 1. ** 但旋轉處理的方式是一致的,只是旋轉之後的平衡因子不一致. **注意事項: ** 1. 圖(1)中A表示右邊失衡的結點 圖(2)表示平衡處理的結果 **作 者: # Qifeng.zou # 2013.12.13 # ******************************************************************************/int avl_rl_balance(avl_tree_t *tree, avl_node_t *node){ avl_node_t *rchild = node->rchild, *parent = node->parent, *rlchild = NULL; 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_lchild(rchild, rlchild->rchild); avl_set_rchild(rlchild, rchild); avl_set_rchild(node, rlchild->lchild); avl_set_lchild(rlchild, node); avl_instead_child(tree, parent, node, rlchild); return AVL_SUCCESS;}
程式碼11 RL型平衡化處理(內部介面)
/****************************************************************************** **函式名稱: avl_right_balance **功 能: 對右邊失去平衡的結點進行平衡化處理(內部介面) **輸入引數: ** tree: 平衡二叉樹 ** node: 右邊失去平衡的結點 **輸出引數: NONE **返 回: AVL_SUCCESS:成功 AVL_FAILED:失敗 **實現描述: ** 場景1: RR型 ** A C ** / \ / \ ** AL C -> A CR ** / \ / \ \ ** CL CR AL CL X ** \ ** X ** (1) (2) ** 說明: 結點A是失衡結點,此時當結點C的平衡因子為-1時,可判斷為RR型。 ** 場景2: RL型 ** A B ** / \ / \ ** AL C -> A C ** / \ / \ / \ ** B CR AL BL BR CR ** / \ ** BL BR ** (1) (2) ** 說明: 結點A是失衡結點,此時當結點C的平衡因子為1時,可判斷為RL型。 ** 雖然此時結點B的平衡因子的值可能為:-1, 0, 1. ** 但旋轉處理的方式是一致的,只是旋轉之後的平衡因子不一致. **注意事項: ** 1. 圖(1)中A表示右邊失衡的結點 圖(2)表示平衡處理的結果 **作 者: # Qifeng.zou # 2013.12.13 # ******************************************************************************/static int avl_right_balance(avl_tree_t *tree, avl_node_t *node){ avl_node_t *rchild = node->rchild; switch(rchild->bf) { case AVL_RH: /* 場景1: RR型 - 向左旋轉 */ { return avl_rr_balance(tree, node); } case AVL_LH: /* 場景2: RL型 - 先向右旋轉 再向左旋轉 */ { return avl_rl_balance(tree, node); } } return AVL_FAILED;}
程式碼12 RR型和RL平衡化處理(內部介面)
/****************************************************************************** **函式名稱: avl_ll_balance **功 能: LL型平衡化處理 - 向右旋轉(內部介面) **輸入引數: ** tree: 平衡二叉樹 ** node: 左邊失去平衡的結點 **輸出引數: NONE **返 回: AVL_SUCCESS:成功 AVL_FAILED:失敗 **實現描述: ** 場景1: LL型 ** A B ** / \ / \ ** B C -> BL A ** / \ / / \ ** BL BR X BR C ** / ** X ** (1) (2) ** 說明: 結點A是失衡結點,此時當結點B的平衡因子為1時,可判斷為LL型。 **注意事項: ** 1. 圖(1)中A表示左邊失衡的結點 圖(2)表示平衡處理的結果 **作 者: # Qifeng.zou # 2013.12.13 # ******************************************************************************/int avl_ll_balance(avl_tree_t *tree, avl_node_t *node){ avl_node_t *lchild = node->lchild, *parent = node->parent; avl_set_lchild(node, lchild->rchild); node->bf = AVL_EH; avl_set_rchild(lchild, node); lchild->bf = AVL_EH; avl_instead_child(tree, parent, node, lchild); return AVL_SUCCESS;}
程式碼13 LL型平衡化處理(內部介面)
/****************************************************************************** **函式名稱: avl_lr_balance **功 能: LR型平衡化處理 - 先左旋轉 再向右旋轉(內部介面) **輸入引數: ** tree: 平衡二叉樹 ** node: 左邊失去平衡的結點 **輸出引數: NONE **返 回: AVL_SUCCESS:成功 AVL_FAILED:失敗 **實現描述: ** 場景1: LL型 ** A B ** / \ / \ ** B C -> BL A ** / \ / / \ ** BL BR X BR C ** / ** X ** (1) (2) ** 說明: 結點A是失衡結點,此時當結點B的平衡因子為1時,可判斷為LL型。 ** 場景2: LR型 ** A C ** / \ / \ ** B AR -> B A ** / \ / \ / \ ** BL C BL CL CR AR ** / \ ** CL CR ** (1) (2) ** 說明: 結點A是失衡結點,此時當結點B的平衡因子為-1時,可判斷為LR型。 ** 雖然此時結點C的平衡因子的值可能為:-1, 0, 1. ** 但旋轉處理的方式是一致的,只是旋轉之後的平衡因子不一致. **注意事項: ** 1. 圖(1)中A表示左邊失衡的結點 圖(2)表示平衡處理的結果 **作 者: # Qifeng.zou # 2013.12.13 # ******************************************************************************/int avl_lr_balance(avl_tree_t *tree, avl_node_t *node){ avl_node_t *lchild = node->lchild, *parent = node->parent, *lrchild = NULL; 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_rchild(lchild, lrchild->lchild); avl_set_lchild(lrchild, lchild); avl_set_lchild(node, lrchild->rchild); avl_set_rchild(lrchild, node); avl_instead_child(tree, parent, node, lrchild); return AVL_SUCCESS;}
程式碼14 LR型平衡化處理(內部介面)
/****************************************************************************** **函式名稱: avl_left_balance **功 能: 對左邊失去平衡的結點進行平衡化處理(內部介面) **輸入引數: ** tree: 平衡二叉樹 ** node: 左邊失去平衡的結點 **輸出引數: NONE **返 回: AVL_SUCCESS:成功 AVL_FAILED:失敗 **實現描述: ** 場景1: LL型 ** A B ** / \ / \ ** B C -> BL A ** / \ / / \ ** BL BR X BR C ** / ** X ** (1) (2) ** 說明: 結點A是失衡結點,此時當結點B的平衡因子為1時,可判斷為LL型。 ** 場景2: LR型 ** A C ** / \ / \ ** B AR -> B A ** / \ / \ / \ ** BL C BL CL CR AR ** / \ ** CL CR ** (1) (2) ** 說明: 結點A是失衡結點,此時當結點B的平衡因子為-1時,可判斷為LR型。 ** 雖然此時結點C的平衡因子的值可能為:-1, 0, 1. ** 但旋轉處理的方式是一致的,只是旋轉之後的平衡因子不一致. **注意事項: ** 1. 圖(1)中A表示左邊失衡的結點 圖(2)表示平衡處理的結果 **作 者: # Qifeng.zou # 2013.12.13 # ******************************************************************************/int avl_left_balance(avl_tree_t *tree, avl_node_t *node){ avl_node_t *lchild = node->lchild; switch(lchild->bf) { case AVL_LH: /* 場景1: LL型 */ { return avl_ll_balance(tree, node); } case AVL_RH: /* 場景2: LR型 */ { return avl_lr_balance(tree, node); } } return AVL_FAILED;}
程式碼15 LL型和LR型平衡處理(內部介面)