二叉排序平衡樹查找-動态查找
更新:2013-12-02
//
需要掌握的前提知識是二叉排序樹查找
名稱:二叉排序平衡樹查找
關鍵字:二叉平衡樹,二叉排序樹
在二叉排序樹的不斷插入查找中,有可能出現極端情況,最壞情況例如:
每棵樹都隻有右孩子。那樣查找變成了順序查找了。
為了使排序樹的深度最小,需要對每棵樹的左右孩子進行平衡處理,成為
二叉平衡樹。
二叉平衡樹:它是一棵空樹,或者,它的左孩子或右孩子不為空的話也為
平衡二叉樹。二叉平衡樹的左孩子和右孩子的深度差絕對值不能超過1。
如果将平衡因子設為左子樹的深度減去右子樹的深度,那麼值有-1,0,1。
讨論一下插入結點時的平衡操作。
插入結點可以分為兩種:
一插入根結點,二插入葉子結點。
插入根結點較為簡單,着重看插入葉子結點的情況。
(1)設有樹tree1:
a(2) b(0)
/ \ / \
b(1) ar => bl a(0)
/ \ ^ / \
bl br c br ar
^
c
或樹tree1':
a(2) b(0)
/ / \
b(1) => bl a(0)
/
^
bl
因為插入新結點而使樹的平衡因子變為2,在這種情況下,需要作
單向右旋平衡處理:
b和a交換位置,a接管b的右孩子br,那樣樹就平衡了,并且樹的結點被中
序周遊的順序不變。
(2)設有樹tree2:
a(-2) b(0)
/ \ / \
al b(-1) => a(0) br
/ \ / \ ^
bl br al bl c
^
c
或樹tree2':
a(-2) b(0)
\ / \
b(-1) => a(0) br
\
^
br
因為插入新結點而使樹的平衡因子變為-2,在這種情況下,需要作
單向左旋平衡處理:
b和a交換位置,a接管b的左孩子bl,那樣樹就平衡了,并且樹的結點被中序
周遊的順序不變。因為不确定bl是否為NULL,是以a的平衡因子不确定。
(3)設有樹tree3:
a(2) a(2) ____br(0)____
/ \ / \ / \
b(-1) ar => br(?) ar => b(?) a(?)
/ \ / \ / \ / \
bl br(?) b(?) null\d bl c\null null\d ar
/ \ / \
c\null null\d bl c\null
或樹tree3':
a(2) a(2) br(0)
/ / / \
b(-1) => br(1) => b(0) a(0)
\ /
^ b(0)
br
因為插入新結點或d結點而使樹的平衡因子變為2,在這種情況下,需要作
雙向旋轉(先左後右)平衡處理:先b和br作單向左旋,然後br和a作單向
右旋,那樣樹就平衡了。并且樹的結點被中序遍的順序不變。
(4)設有樹tree4:
a(-2) a(-2) ____bl(0)____
/ \ / \ / \
al b(1) => al bl(?) => a(?) b(?)
/ \ / \ / \ / \
bl(?) br c\null b(?) al c\null null\d br
/ \ / \
c\null null\d null\d br
或樹tree4':
a(-2) a(-2) bl(0)
\ \ / \
b(1) => bl(-1) => a(0) b(0)
/ \
^ b(0)
bl
因為插入新結點或d結點而使樹的平衡因子變為-2,在這種情況下,需要作
雙向旋轉(先右後左)平衡處理:先b和bl作單向右旋,然後br和a作單向
左旋,那樣樹就平衡了。并且樹的結點被中序遍的順序不變。
讨論一下删除結點時的平衡操作。
因為要保持有序性,是以對二叉排序平衡樹的結點的删除操作和對
二叉排序樹的删除操作類似,不過二叉排序平衡樹還需要作平衡操作。
對樹進行中序周遊,就可以得到結點的從小到大的排序,
如果被删結點隻有一棵子樹,直接用子樹取代被删除結點即可。
如果被删結點有兩棵子樹,那麼可以考慮用其前驅或後繼來替代該結點。
在二叉排序樹中假設有這麼一棵樹:
a
/ \
b c
/ \
d e
/ \ / \
f g h i
/ \
j k
但在二叉排序平衡樹中不會出現這樣的情況的,因為上面的樹不平衡。
現在標明用前驅代替删除結點,是以現在重新假設有樹:
_a(?)_
/ \
b(?) c(?)/null
/ \ ^
d(?)/null e(?)/null ...
/ \ ^
f/null g(?)/null ...
/
j/null
在上圖中假設c子樹和e子樹的高度能使a樹平衡。
這樣就可以分情況分析b點的删除對樹平衡的影響了。
現在要删除的結點是b,對該樹的中序周遊順序為fdjgb...a...。
(1)如果b隻有左孩子
很明顯,隻要将b的左孩子直接連接配接到b的前驅,再将b結點删除。
那麼它此時的a的平衡因子就會減1,a樹的高度也發生了變化,
此時要回溯到最小不平衡樹,進行平衡調整,因為調整可能導緻
最小不平衡樹的父節點不平衡,是以要繼續回溯調整,直到根結
點。增加高度的旋轉操作和降低高度的旋轉操作對稱。單左旋和
右左旋剛好為對稱操作。
(2)如果b隻有右孩子
同理,隻要将b的右孩子直接連接配接到b的前驅,在将b結點删除。
a樹的高度發生變化,然後進行相應的旋轉操作。
(3)如果b的左右孩子均不為空
同BST樹的删除操作,删除後的樹為:
__a(?)__
/ \
g(?) c(?)/null
/ \ ^
d(?)/null e(?)/null ...
/ \ ^
f/null j/null ...
或當g為空時:
__a(?)__
/ \
__d(?)__ c(?)/null
/ \ ^
f/null e(?)/null ...
^
...
由以上看到,發生高度變化的樹有d樹,g樹,a樹。需要進行調整。
平衡因子的變化:
如果b的左孩子或右孩子為空,回溯調整a。
如果g為空,删除b後,則d的平衡因子不變,回溯調整a。
如果g不為空,删除b後,d的平衡因子加1,回溯調整g和a。
g的平衡因子取b原來的平衡因子。
是以樹的平衡調整從d結點開始,到g,再到a。也就是說,
是在替換删除結點的結點開始平衡調整的。為了代碼友善,
這種情況就隻是用替換結點的值對删除結點的值進行替換,
實際删除的結點是替換點(便于程式設計實作回溯)。但平衡
因子不能被替換。
源代碼的測試暫未發現錯誤。
附源代碼:
//樹的平衡因子常量
#define LSZ_SEARCH_BALANCEDIFF 1
#define LSZ_SEARCH_LEFTHIGH 1
#define LSZ_SEARCH_EQUALHIGH \
((LSZ_SEARCH_LEFTHIGH)-(LSZ_SEARCH_BALANCEDIFF))
#define LSZ_SEARCH_RIGHTHIGH \
((LSZ_SEARCH_EQUALHIGH)-(LSZ_SEARCH_BALANCEDIFF))
//樹的高度是否增加
#define LSZ_SEARCH_HIGHERTRUE 1
#define LSZ_SEARCH_HIGHERFALSE 0
//樹的高度是否減少
#define LSZ_SEARCH_LOWERTRUE 1
#define LSZ_SEARCH_LOWERFALSE 0
//删除樹結點的宏
#define LSZ_SEARCH_DESTROYNODE(node) (free((node)->element),free(node))
//初始化樹頭結點的宏
#define LSZ_SEARCH_TREEINIT(tree) ((tree).lChild=NULL)
//元素的關鍵字類型
typedef int LSZ_SearchK; //key
//元素類型
typedef struct LSZ_SearchE{ //element
LSZ_SearchK key; //元素的鍵值,用于唯一辨別
}LSZ_SearchE;
//帶平衡因子的非順序存儲的查找樹的結構元素類型
//二叉平衡樹的結點類型
typedef struct LSZ_SearchBBTNode{ //binary,balance, tree
int fBalance; //factor of balance
LSZ_SearchE *element;
struct LSZ_SearchBBTNode *lChild;
struct LSZ_SearchBBTNode *rChild;
}LSZ_SearchBBTNode;
//二叉排序平衡樹查找
//
//tree:其左孩子指向樹的根結點。
//element:當往樹中插入結點時,其指向空間存放傳入值,
// 其它情況指向空間用于記錄查找到元素的值。
//mode:僅查找,或帶插入的查找,或帶删除的查找。
int LSZ_search_treeBinarySortedBalanced(LSZ_SearchBBTNode *tree,
LSZ_SearchE *element,
int mode)
{
int depth = LSZ_SEARCH_HIGHERFALSE; //标記樹的深度的變化
LSZ_SearchBBTNode *treeTemp = tree->lChild; //取得根結點
switch(mode){
case LSZ_SEARCH_ONLY:
while(treeTemp != NULL){
if(treeTemp->element->key == element->key){
*element = *(treeTemp->element);
return 0; //查找成功
}
else{
treeTemp = element->key < treeTemp->element->key
? treeTemp->lChild : treeTemp->rChild;
}
}
break;
case LSZ_SEARCH_INSERT: //插入查找需要堆棧
return LSZ_search_insertBSBT(&(tree->lChild), element, &depth); //這裡沒有初始化右子樹
break;
case LSZ_SEARCH_DELETE: //删除查找需要堆棧
return LSZ_search_deleteBSBT(&(tree->lChild), element, &depth);
break;
}
return -1; //出錯
}
//二叉排序平衡樹帶插入的查找
//
//(*tree):指向需要被查找的樹的樹根的指針的位址。
// 注意(*tree)必須在原來的樹結構中并指向樹根。
//element:指向空間存放要插入結點的值,或已存在結點的傳出值。
//isHigher:在執行插入函數後,傳回樹是否長高。
int LSZ_search_insertBSBT(LSZ_SearchBBTNode **tree,
LSZ_SearchE *element,
int *isHigher)
{
int result; //記錄調用插入函數的傳回值,判斷是否執行了插入操作
if((*tree) == NULL){ //空樹
if((*tree = (LSZ_SearchBBTNode*)malloc(
sizeof(LSZ_SearchBBTNode))) == NULL){
return -1;
}
if(((*tree)->element = (LSZ_SearchE*)malloc(
sizeof(LSZ_SearchE))) == NULL){
free(*tree);
return -1;
}
(*tree)->lChild = (*tree)->rChild = NULL;
*((*tree)->element) = *element;
(*tree)->fBalance = LSZ_SEARCH_EQUALHIGH; //新插入結點的平衡因子
*isHigher = LSZ_SEARCH_HIGHERTRUE; //樹的高度發生變化
return 1;
}
else{ //樹非空
if((*tree)->element->key == element->key){
*element = *((*tree)->element);
*isHigher = LSZ_SEARCH_HIGHERFALSE;
return 0; //已存在結點,查找成功
}
if((*tree)->element->key > element->key){ //往左子樹中插入
if((result = LSZ_search_insertBSBT(&((*tree)->lChild), element, isHigher)) == -1){
return -1; //插入失敗
}
if(*isHigher == LSZ_SEARCH_HIGHERTRUE){ //左子樹變高,需要修改平衡因子和判斷整棵樹是否長高
switch((*tree)->fBalance){ //原來樹根的平衡因子
case LSZ_SEARCH_LEFTHIGH:
LSZ_search_lBalanceBSBT(tree); //需要進行左平衡處理
*isHigher = LSZ_SEARCH_HIGHERFALSE;
break;
case LSZ_SEARCH_EQUALHIGH:
(*tree)->fBalance = LSZ_SEARCH_LEFTHIGH;
*isHigher = LSZ_SEARCH_HIGHERTRUE;
break;
case LSZ_SEARCH_RIGHTHIGH:
(*tree)->fBalance = LSZ_SEARCH_EQUALHIGH;
*isHigher = LSZ_SEARCH_HIGHERFALSE;
break;
}
}
}
else{ //往右子樹插入
if((result = LSZ_search_insertBSBT(&((*tree)->rChild), element, isHigher)) == -1){
return -1; //插入失敗
}
if(*isHigher == LSZ_SEARCH_HIGHERTRUE){ //右子樹變高,需要修改平衡因子和判斷整棵樹是否長高
switch((*tree)->fBalance){ //原來樹根的平衡因子
case LSZ_SEARCH_LEFTHIGH:
(*tree)->fBalance = LSZ_SEARCH_EQUALHIGH;
*isHigher = LSZ_SEARCH_HIGHERFALSE;
break;
case LSZ_SEARCH_EQUALHIGH:
(*tree)->fBalance = LSZ_SEARCH_RIGHTHIGH;
*isHigher = LSZ_SEARCH_HIGHERTRUE;
break;
case LSZ_SEARCH_RIGHTHIGH:
LSZ_search_rBalanceBSBT(tree); //需要進行右平衡處理
*isHigher = LSZ_SEARCH_HIGHERFALSE;
break;
}
}
}
return result;
}
}
//二叉排序平衡樹帶删除的查找
//
//(*tree):指向需要被查找的樹的樹根的指針的位址。
// 注意(*tree)必須在原來的樹結構中并指向樹根。
//element:指向空間存放要删除結點的值。
//isHigher:在執行插入函數後,傳回樹是否長高。
int LSZ_search_deleteBSBT(LSZ_SearchBBTNode **tree,
LSZ_SearchE *element,
int *isLower)
{
int result; //記錄調用删除函數的傳回值,判斷是否執行了删除操作
LSZ_SearchBBTNode *nodeDelete, **nodeFind;
LSZ_SearchE *elementTemp; //用于替換結點和删除結點的交換
if((*tree) == NULL){ //空樹
*isLower = LSZ_SEARCH_LOWERFALSE; //樹的高度未發生變化
return 0;
}
else{ //樹非空
if((*tree)->element->key == element->key){ //找到删除結點
nodeDelete = *tree;
*element = *((*tree)->element);
//下面的删除要按照二叉排序樹的删除方法
if(nodeDelete->lChild == NULL || nodeDelete->rChild == NULL){
if(nodeDelete->lChild == NULL){
*tree = nodeDelete->rChild;
if(*tree != NULL){ //相當于少一右孩子,防止删除根結點平衡因子不更新出錯
(*tree)->fBalance += LSZ_SEARCH_BALANCEDIFF;
} //因為要操作平衡因子,防止兩結點都為空的情況
}
else{ //删除結點無右孩子
*tree = nodeDelete->lChild;
if(*tree != NULL){ //相當于少一左孩子,防止删除根結點平衡因子不更新出錯
(*tree)->fBalance -= LSZ_SEARCH_BALANCEDIFF;
} //因為要操作平衡因子,防止兩結點都為空的情況
}
*isLower = LSZ_SEARCH_LOWERTRUE; //高度變化
LSZ_SEARCH_DESTROYNODE(nodeDelete);
return 1; //删除成功
}
else{ //删除結點同時有左右孩子,注意平衡因子的調整
nodeFind = &(nodeDelete->lChild);
if((*nodeFind)->rChild == NULL){ //删除結點的後繼的特殊情況
*tree = (*nodeFind);
(*tree)->fBalance -= 1; //相當于少一左孩子,防止删除根結點平衡因子出錯
(*tree)->rChild = nodeDelete->rChild;
LSZ_SEARCH_DESTROYNODE(nodeDelete);
*isLower = LSZ_SEARCH_LOWERTRUE; //高度變化
return 1;
}
else{ //删除結點後繼的一般情況
while((*nodeFind)->rChild != NULL){
(*nodeFind) = (*nodeFind)->rChild;
}
//删除結點的值賦予替換結點的值,但不包括平衡因子
(*nodeFind)->fBalance = nodeDelete->fBalance;
elementTemp = nodeDelete->element; //交換元素
nodeDelete->element = (*nodeFind)->element;
(*nodeFind)->element = elementTemp;
goto ToDeleteLChild; //為了利用函數遞歸的堆棧,強制轉跳,最終要删除的結點為替換結點
} //一般情況
} //删除結點同時有左右孩子
} //找到删除結點
if((*tree)->element->key > element->key){ //往左子樹中删除
ToDeleteLChild: result = LSZ_search_deleteBSBT(&((*tree)->lChild), element, isLower);
if(*isLower == LSZ_SEARCH_LOWERTRUE){ //左子樹變矮,需要修改平衡因子和判斷整棵樹是否變矮
switch((*tree)->fBalance){ //原來樹根的平衡因子
case LSZ_SEARCH_LEFTHIGH:
(*tree)->fBalance = LSZ_SEARCH_EQUALHIGH;
*isLower = LSZ_SEARCH_LOWERTRUE;
break;
case LSZ_SEARCH_EQUALHIGH:
(*tree)->fBalance = LSZ_SEARCH_RIGHTHIGH;
*isLower = LSZ_SEARCH_LOWERFALSE;
break;
case LSZ_SEARCH_RIGHTHIGH:
LSZ_search_rBalanceBSBT(tree); //需要進行右平衡處理
*isLower = LSZ_SEARCH_LOWERTRUE;
break;
}
}
} //往左子樹删除
else{ //往右子樹删除
result = LSZ_search_deleteBSBT(&((*tree)->rChild), element, isLower);
if(*isLower == LSZ_SEARCH_LOWERTRUE){ //右子樹變矮,需要修改平衡因子和判斷整棵樹是否變矮
switch((*tree)->fBalance){ //原來樹根的平衡因子
case LSZ_SEARCH_LEFTHIGH:
LSZ_search_lBalanceBSBT(tree); //需要進行左平衡處理
*isLower = LSZ_SEARCH_LOWERTRUE;
break;
case LSZ_SEARCH_EQUALHIGH:
(*tree)->fBalance = LSZ_SEARCH_LEFTHIGH;
*isLower = LSZ_SEARCH_LOWERFALSE;
break;
case LSZ_SEARCH_RIGHTHIGH:
(*tree)->fBalance = LSZ_SEARCH_EQUALHIGH;
*isLower = LSZ_SEARCH_LOWERTRUE;
break;
}
}
} //往右子樹删除
return result;
} //樹非空
}
//左子樹(長高)導緻失衡時的左平衡調整函數
//
//(*tree):指向最小的失衡樹的樹根。
// 注意(*tree)必須在原來的樹結構中。
void LSZ_search_lBalanceBSBT(LSZ_SearchBBTNode **tree)
{
LSZ_SearchBBTNode **lChild;
lChild = &((*tree)->lChild); //最小非平衡樹的左孩子
switch((*lChild)->fBalance){ //檢查左子樹,确定單向旋轉還是雙旋轉
case LSZ_SEARCH_LEFTHIGH: //新結點插入在左左孩子上,隻需單向右旋
(*tree)->fBalance = (*lChild)->fBalance = LSZ_SEARCH_EQUALHIGH;
LSZ_search_rRotateBSBT(tree); //經過旋轉,(*tree)已改變
break;
case LSZ_SEARCH_RIGHTHIGH: //新結點插入在左右孩子上,需要進行雙旋轉
switch((*lChild)->rChild->fBalance){ //需要判斷和調整平衡因子
case LSZ_SEARCH_LEFTHIGH: //新結點插入在左右孩子的左孩子上
(*tree)->fBalance = LSZ_SEARCH_RIGHTHIGH;
(*lChild)->fBalance = LSZ_SEARCH_EQUALHIGH;
break;
case LSZ_SEARCH_EQUALHIGH:
(*tree)->fBalance = (*lChild)->fBalance = LSZ_SEARCH_EQUALHIGH;
break;
case LSZ_SEARCH_RIGHTHIGH:
(*tree)->fBalance = LSZ_SEARCH_EQUALHIGH;
(*lChild)->fBalance = LSZ_SEARCH_LEFTHIGH;
break;
}
(*lChild)->rChild->fBalance = LSZ_SEARCH_EQUALHIGH;
LSZ_search_lRotateBSBT(lChild);
LSZ_search_rRotateBSBT(tree);
break;
}
}
//右子樹(長高)導緻失衡時的左平衡調整函數
//
//(*tree):指向最小的失衡樹的樹根。
// 注意(*tree)必須在原來的樹結構中。
void LSZ_search_rBalanceBSBT(LSZ_SearchBBTNode **tree)
{
LSZ_SearchBBTNode **rChild;
rChild = &((*tree)->rChild); //最小非平衡樹的右孩子
switch((*rChild)->fBalance){ //檢查左子樹,确定單向旋轉還是雙旋轉
case LSZ_SEARCH_LEFTHIGH: //新結點插入在右左孩子上,需要進行雙旋轉
switch((*rChild)->lChild->fBalance){ //需要判斷和調整平衡因子
case LSZ_SEARCH_LEFTHIGH:
(*tree)->fBalance = LSZ_SEARCH_EQUALHIGH;
(*rChild)->fBalance = LSZ_SEARCH_RIGHTHIGH;
break;
case LSZ_SEARCH_EQUALHIGH:
(*tree)->fBalance = (*rChild)->fBalance = LSZ_SEARCH_EQUALHIGH;
break;
case LSZ_SEARCH_RIGHTHIGH:
(*tree)->fBalance = LSZ_SEARCH_LEFTHIGH;
(*rChild)->fBalance = LSZ_SEARCH_EQUALHIGH;
break;
}
(*rChild)->lChild->fBalance = LSZ_SEARCH_EQUALHIGH;
LSZ_search_rRotateBSBT(rChild);
LSZ_search_lRotateBSBT(tree);
break;
case LSZ_SEARCH_RIGHTHIGH: //新結點插入在右右孩子上,隻需單向左旋
(*tree)->fBalance = (*rChild)->fBalance = LSZ_SEARCH_EQUALHIGH;
LSZ_search_lRotateBSBT(tree); //經過旋轉,(*tree)已改變
break;
}
}
//單向左旋平衡操作函數
//
//(*tree):指向需要旋轉的樹的樹根。
// 注意(*tree)必須在原來的樹結構中并指向樹根。
void LSZ_search_lRotateBSBT(LSZ_SearchBBTNode **tree)
{
LSZ_SearchBBTNode *axesNode; //旋轉軸結點
axesNode = (*tree)->rChild; //取得旋轉軸結點
(*tree)->rChild = axesNode->lChild; //旋轉結點接管軸結點的左孩子
axesNode->lChild = *tree; //旋轉軸結點指向原根結點
*tree = axesNode; //旋轉軸作為新根結點
}
//單向右旋平衡操作函數
//
//(*tree):指向需要旋轉的樹的樹根。
// 注意(*tree)必須在原來的樹結構中并指向樹根。
void LSZ_search_rRotateBSBT(LSZ_SearchBBTNode **tree)
{
LSZ_SearchBBTNode *axesNode; //旋轉軸結點
axesNode = (*tree)->lChild; //取得旋轉軸結點
(*tree)->lChild = axesNode->rChild; //旋轉結點接管軸結點的右孩子
axesNode->rChild = *tree; //旋轉軸結點指向原根結點
*tree = axesNode; //旋轉軸作為新根結點
}
//銷毀二叉排序平衡樹
//
//tree:指向樹的頭結點的指針,樹的銷毀用後續周遊。
// 這裡用遞歸實作後根周遊。
void LSZ_search_destroyBSBT(LSZ_SearchBBTNode *tree)
{
if(tree->lChild != NULL)
LSZ_search_doDestroyBSBT(tree->lChild);
tree->lChild = NULL; //重新設定空指向
}
void LSZ_search_doDestroyBSBT(LSZ_SearchBBTNode *tree)
{
if(tree->lChild != NULL){ //先銷毀左子樹
LSZ_search_doDestroyBSBT(tree->lChild);
}
if(tree->rChild != NULL){ //再銷毀右子樹
LSZ_search_doDestroyBSBT(tree->rChild);
}
//最後銷毀根結點
LSZ_SEARCH_DESTROYNODE(tree);
}
本部落格的版權聲明 點選打開連結