【筆記】平衡二叉樹
若二叉排序樹的深度為n,在最壞的情況下平均查詢長度為n,為了減少二叉排序樹的查詢次數,需要對二叉排序樹進行平衡化處理,平衡化處理後得到的二叉樹稱為平衡二叉樹。
1.平衡二叉樹的定義
平衡二叉樹又稱為AVL樹,它或者是一棵空樹,或者左子樹和右子樹都是平衡二叉樹,且左子樹和右子樹的深度之差的絕對值不超過1。
若將二叉樹中結點的平衡因子BF定義為結點的左子樹的深度和右子樹的深度,則平衡二叉樹中每個結點平衡因子只可能是-1、0、1。如下圖為兩棵平衡二叉樹,結點的右邊數值表示平衡因子,因為該二叉樹既是二叉排序樹又是平衡樹,因此其為平衡二叉樹。
只要二叉樹上有一個結點的平衡因子的絕對值大於1,則該二叉樹就是不平衡的,如下圖所示。
如果二叉排序樹是平衡二叉樹,則其平均查詢長度與
2.二叉排序樹的平衡處理
假設有一個關鍵字序列{5,34,45,76,65},依照此關鍵字序列建立二叉排序樹,且使該二叉排序樹是平衡二叉排序樹,構造過程如下圖所示。
一般情況下,假設由於在二叉排序樹上插入結點而失去平衡的最小子樹根結點的指標為a(即a是裡插入結點最近,且平衡因子絕對值超過1的祖先結點),則失去平衡後進行調整的規律可歸納為下列4種情況:
1.單向右旋平衡處理:由於在*a的左子樹根結點的左子樹上插入結點,*a的平衡因子由1增至2,致使以*a為根的子樹失去平衡,則需進行一次向右的順時針旋轉操作;
2.單向左旋平衡處理:由於在*a的右子樹根結點的右子樹插入結點,*a的平衡因子由-1變成-2,致使以*a為根結點的子樹失去平衡,則需進行一次向左的逆時針操作;
3.雙向旋轉(先左後右)平衡處理:由於在*a的左子樹結點的右子樹上插入結點,*a的平衡因子由1增至2,致使以*a為根結點的子樹失去平衡,則需進行兩次旋轉操作(先左旋後右旋);
4.雙向旋轉(先右後左)平衡處理:由於在*a的右子樹根結點的左子樹上插入結點,*a的平衡因子由-1變成-2,致使以*a為根結點的子樹失去平衡,則需進行兩次旋轉操作(先右旋後左旋)。
上述4種情況中,1和2對稱,3和4對稱。旋轉操作的正確性容易由“保持二叉排序樹的特性:中序遍歷所得關鍵字序列自小至大有序”證明。無論哪一種情況,在經過平衡旋轉處理之後,以*b或*c為根的新子樹為平衡二叉樹,而且它的深度和插入之前以*a為根的子樹相同。因此當平衡的二叉排序樹因插入結點而失去平衡時,僅需對最小不平衡子樹進行平衡旋轉處理即可。因為經過旋轉處理之後的子樹深度和插入之前相同,因而不影響插入路徑上所有祖先結點的平衡度。
在平衡二叉排序樹BBST上插入一個新的資料元素e的遞迴演算法可描述如下:
- BBST為空樹,則插入一個資料元素為e的新結點作為BBST的根結點,樹的深度增1;
- 若e的關鍵字和BBST的根結點的關鍵字相等,則不進行插入;
- 若e的關鍵字小於BBST的根結點的關鍵字,而且在BBST的左子樹中不存在和e有相同關鍵字的結點,則將e插入在BBST 的左子樹上,並且當插入之後的左子樹深度增加時,分別就下列不同情況處理:
(1)BBST的根結點的平衡因子為-1:則將根結點的平衡因子更改為0,BBST的深度不變;
(2)BBST的根結點的平衡因子為0:則將根結點的平衡因子更改為1,BBST的深度增1;
(3)BBST的根結點的平衡因子為1:若BBST的左子樹根結點的平衡因子為1,則需進行單向右旋平衡處理,並且在右旋處理之後,將根結點和其右子樹根結點的平衡因子更改為0,樹的深度不變。
若BBST的左子樹根結點的平衡因子為-1,則需進行先向左、後向右的雙向旋轉平衡處理,並且在旋轉處理之後,修改根結點和其左、右子樹根結點的平衡因子,樹的深度不變;- 若e的關鍵字大於BBST的根結點的關鍵字,而且在BBST的右子樹中不睬做和e有相同關鍵字的即誒但那,則將e插入在BBST的右子樹上,並且當出入之後的右子樹深度增加時,分別就不同情況處理。其處理操作和3中所述相對稱。
3.平衡二叉樹的實現
- 型別定義
#define LH +1 /* 左高 */
#define EH 0 /* 等高 */
#define RH -1 /* 右高 */
typedef struct BSTNode
{
ElemType data;
int bf; /* 結點的平衡因子 */
struct BSTNode *lchild,*rchild; /* 左、右孩子指標 */
}BSTNode,*BSTree;
- 初始化查詢表
Status InitDSTable(BSTree *DT)
{ /* 操作結果: 構造一個空的動態查詢表DT */
*DT=NULL;
return OK;
}
- 銷燬查詢表
void DestroyDSTable(BSTree *DT)
{ /* 初始條件: 動態查詢表DT存在。操作結果: 銷燬動態查詢表DT */
if(*DT) /* 非空樹 */
{
if((*DT)->lchild) /* 有左孩子 */
DestroyDSTable(&(*DT)->lchild); /* 銷燬左孩子子樹 */
if((*DT)->rchild) /* 有右孩子 */
DestroyDSTable(&(*DT)->rchild); /* 銷燬右孩子子樹 */
free(*DT); /* 釋放根結點 */
*DT=NULL; /* 空指標賦0 */
}
}
- 查詢操作
BSTree SearchBST(BSTree T,KeyType key)
{ /* 在根指標T所指二叉排序樹中遞迴地查詢某關鍵字等於key的資料元素, */
/* 若查詢成功,則返回指向該資料元素結點的指標,否則返回空指標。 */
if((!T)||EQ(key,T->data.key))
return T; /* 查詢結束 */
else if LT(key,T->data.key) /* 在左子樹中繼續查詢 */
return SearchBST(T->lchild,key);
else
return SearchBST(T->rchild,key); /* 在右子樹中繼續查詢 */
}
- 遍歷操作
void TraverseDSTable(BSTree DT,void(*Visit)(ElemType))
{ /* 初始條件: 動態查詢表DT存在,Visit是對結點操作的應用函式 */
/* 操作結果: 按關鍵字的順序對DT的每個結點呼叫函式Visit()一次且至多一次 */
if(DT)
{
TraverseDSTable(DT->lchild,Visit); /* 先中序遍歷左子樹 */
Visit(DT->data); /* 再訪問根結點 */
TraverseDSTable(DT->rchild,Visit); /* 最後中序遍歷右子樹 */
}
}
- 插入操作
Status InsertAVL(BSTree *T,ElemType e,Status *taller)
{ /* 若在平衡的二叉排序樹T中不存在和e有相同關鍵字的結點,則插入一個 */
/* 資料元素為e的新結點,並返回1,否則返回0。若因插入而使二叉排序樹 */
/* 失去平衡,則作平衡旋轉處理,布林變數taller反映T長高與否。 */
if(!*T)
{ /* 插入新結點,樹“長高”,置taller為TRUE */
*T=(BSTree)malloc(sizeof(BSTNode));
(*T)->data=e;
(*T)->lchild=(*T)->rchild=NULL;
(*T)->bf=EH;
*taller=TRUE;
}
else
{
if EQ(e.key,(*T)->data.key)
{ /* 樹中已存在和e有相同關鍵字的結點則不再插入 */
*taller=FALSE;
return FALSE;
}
if LT(e.key,(*T)->data.key)
{ /* 應繼續在*T的左子樹中進行搜尋 */
if(!InsertAVL(&(*T)->lchild,e,taller)) /* 未插入 */
return FALSE;
if(*taller) /* 已插入到*T的左子樹中且左子樹“長高” */
switch((*T)->bf) /* 檢查*T的平衡度 */
{
case LH: /* 原本左子樹比右子樹高,需要作左平衡處理 */
LeftBalance(T);
*taller=FALSE;
break;
case EH: /* 原本左、右子樹等高,現因左子樹增高而使樹增高 */
(*T)->bf=LH;
*taller=TRUE;
break;
case RH: (*T)->bf=EH; /* 原本右子樹比左子樹高,現左、右子樹等高 */
*taller=FALSE;
}
}
else
{ /* 應繼續在*T的右子樹中進行搜尋 */
if(!InsertAVL(&(*T)->rchild,e,taller)) /* 未插入 */
return FALSE;
if(*taller) /* 已插入到T的右子樹且右子樹“長高” */
switch((*T)->bf) /* 檢查T的平衡度 */
{
case LH: (*T)->bf=EH; /* 原本左子樹比右子樹高,現左、右子樹等高 */
*taller=FALSE;
break;
case EH: /* 原本左、右子樹等高,現因右子樹增高而使樹增高 */
(*T)->bf=RH;
*taller=TRUE;
break;
case RH: /* 原本右子樹比左子樹高,需要作右平衡處理 */
RightBalance(T);
*taller=FALSE;
}
}
}
return TRUE;
}
- 右旋操作
void R_Rotate(BSTree *p)
{ /* 對以*p為根的二叉排序樹作右旋處理,處理之後p指向新的樹根結點,即旋轉 */
/* 處理之前的左子樹的根結點。 */
BSTree lc;
lc=(*p)->lchild; /* lc指向p的左子樹根結點 */
(*p)->lchild=lc->rchild; /* lc的右子樹掛接為p的左子樹 */
lc->rchild=*p;
*p=lc; /* p指向新的根結點 */
}
- 右平衡旋轉操作
void RightBalance(BSTree *T)
{ /* 對以指標T所指結點為根的二叉樹作右平衡旋轉處理,本演算法結束時, */
/* 指標T指向新的根結點 */
BSTree rc,rd;
rc=(*T)->rchild; /* rc指向*T的右子樹根結點 */
switch(rc->bf)
{ /* 檢查*T的右子樹的平衡度,並作相應平衡處理 */
case RH: /* 新結點插入在*T的右孩子的右子樹上,要作單左旋處理 */
(*T)->bf=rc->bf=EH;
L_Rotate(T);
break;
case LH: /* 新結點插入在*T的右孩子的左子樹上,要作雙旋處理 */
rd=rc->lchild; /* rd指向*T的右孩子的左子樹根 */
switch(rd->bf)
{ /* 修改*T及其右孩子的平衡因子 */
case RH: (*T)->bf=LH;
rc->bf=EH;
break;
case EH: (*T)->bf=rc->bf=EH;
break;
case LH: (*T)->bf=EH;
rc->bf=RH;
}
rd->bf=EH;
R_Rotate(&(*T)->rchild); /* 對*T的右子樹作右旋平衡處理 */
L_Rotate(T); /* 對*T作左旋平衡處理 */
}
}
- 左旋操作
void L_Rotate(BSTree *p)
{ /* 對以*p為根的二叉排序樹作左旋處理,處理之後p指向新的樹根結點,即旋轉 */
/* 處理之前的右子樹的根結點。 */
BSTree rc;
rc=(*p)->rchild; /* rc指向p的右子樹根結點 */
(*p)->rchild=rc->lchild; /* rc的左子樹掛接為p的右子樹 */
rc->lchild=*p;
*p=rc; /* p指向新的根結點 */
}
- 左平衡旋轉操作
void LeftBalance(BSTree *T)
{ /* 對以指標T所指結點為根的二叉樹作左平衡旋轉處理,本演算法結束時, */
/* 指標T指向新的根結點。 */
BSTree lc,rd;
lc=(*T)->lchild; /* lc指向*T的左子樹根結點 */
switch(lc->bf)
{ /* 檢查*T的左子樹的平衡度,並作相應平衡處理 */
case LH: /* 新結點插入在*T的左孩子的左子樹上,要作單右旋處理 */
(*T)->bf=lc->bf=EH;
R_Rotate(T);
break;
case RH: /* 新結點插入在*T的左孩子的右子樹上,要作雙旋處理 */
rd=lc->rchild; /* rd指向*T的左孩子的右子樹根 */
switch(rd->bf)
{ /* 修改*T及其左孩子的平衡因子 */
case LH: (*T)->bf=RH;
lc->bf=EH;
break;
case EH: (*T)->bf=lc->bf=EH;
break;
case RH: (*T)->bf=EH;
lc->bf=LH;
}
rd->bf=EH;
L_Rotate(&(*T)->lchild); /* 對*T的左子樹作左旋平衡處理 */
R_Rotate(T); /* 對*T作右旋平衡處理 */
}
}
相關文章
- 學習筆記——二叉平衡樹(BST)筆記
- 平衡樹學習筆記筆記
- 平衡樹 學習筆記筆記
- 平衡二叉樹二叉樹
- 排序二叉樹和平衡二叉樹排序二叉樹
- 平衡二叉樹(AVL)二叉樹
- 手擼二叉樹——AVL平衡二叉樹二叉樹
- 平衡二叉樹,B樹,B+樹二叉樹
- 平衡二叉樹(AVL樹)和 二叉排序樹轉化為平衡二叉樹 及C語言實現二叉樹排序C語言
- 110. 平衡二叉樹二叉樹
- 十三、Mysql之平衡二叉樹(AVL樹)MySql二叉樹
- 平衡樹和二叉樹的區別二叉樹
- 平衡二叉樹(AVL樹),原來如此!!!二叉樹
- 平衡二叉查詢樹:紅黑樹
- 二叉堆、BST 與平衡樹
- JZ-039-平衡二叉樹二叉樹
- LeetCode-110-平衡二叉樹LeetCode二叉樹
- 二叉樹學習筆記二叉樹筆記
- Java實現紅黑樹(平衡二叉樹)Java二叉樹
- 滿二叉樹、完全二叉樹、平衡二叉樹、二叉搜尋樹(二叉查詢樹)和最優二叉樹二叉樹
- 程式碼隨想錄——二叉樹-12.平衡二叉樹二叉樹
- 資料結構-平衡二叉樹資料結構二叉樹
- 每日一練(28):平衡二叉樹二叉樹
- python實現非平衡二叉樹Python二叉樹
- 二叉平衡樹 python 列表 遞迴Python遞迴
- 自動平衡二叉樹的構建-AVL樹二叉樹
- 二叉樹的遍歷筆記二叉樹筆記
- [Python手撕]判斷平衡二叉樹Python二叉樹
- 手寫AVL平衡二叉搜尋樹
- 遞迴判斷是否二叉平衡樹遞迴
- 夜刷:平衡二叉樹的基本操作二叉樹
- 二叉樹的深度、寬度遍歷及平衡樹二叉樹
- 普通平衡樹學習筆記之Splay演算法筆記演算法
- [學習筆記] Splay & Treap 平衡樹 - 資料結構筆記資料結構
- Java 樹結構實際應用 四(平衡二叉樹/AVL樹)Java二叉樹
- 如何判斷一棵樹是否是二叉平衡樹~
- 資料結構之樹結構概述(含滿二叉樹、完全二叉樹、平衡二叉樹、二叉搜尋樹、紅黑樹、B-樹、B+樹、B*樹)資料結構二叉樹
- JZ79 判斷是不是平衡二叉樹二叉樹
- 24. 平衡二叉樹,及其程式碼實現二叉樹