AVL樹的插入、刪除、旋轉
什麼是AVL樹?
在電腦科學中,AVL樹是最先發明的自平衡二叉查詢樹。在AVL樹中任何節點的兩個兒子子樹的高度最大差別為一,所以它也被稱為高度平衡樹。查詢、插入和刪除在平均和最壞情況下都是O(log n)。增加和刪除可能需要通過一次或多次樹旋轉來重新平衡這個樹。AVL樹得名於它的發明者G.M. Adelson-Velsky和E.M. Landis,他們在1962年的論文《An algorithm for the organization of information》中發表了它。
節點的平衡因子是它的右子樹的高度減去它的左子樹的高度。帶有平衡因子1、0或 -1的節點被認為是平衡的。帶有平衡因子 -2或2的節點被認為是不平衡的,並需要重新平衡這個樹。平衡因子可以直接儲存在每個節點中,或從可能儲存在節點中的子樹高度計算出來。
插入
向AVL樹插入可以通過如同它是未平衡的二叉查詢樹一樣把給定的值插入樹中,接著自底向上向根節點折回,於在插入期間成為不平衡的所有節點上進行旋轉來完成。因為折回到根節點的路途上最多有1.5乘log n個節點,而每次AVL旋轉都耗費恆定的時間,插入處理在整體上耗費O(log n) 時間。
刪除
從AVL樹中刪除可以通過把要刪除的節點向下旋轉成一個葉子節點,接著直接剪除這個葉子節點來完成。因為在旋轉成葉子節點期間最多有log n個節點被旋轉,而每次AVL旋轉耗費恆定的時間,刪除處理在整體上耗費O(log n) 時間。
一樣的進行,所以耗費O(log n)時間,因為AVL樹總是保持平衡的。不需要特殊的準備,樹的結構不會由於查詢而改變。(這是與
實現描述
假設平衡因子是左子樹的高度減去右子樹的高度所得到的值,又假設由於在二叉排序樹上插入結點而失去平衡的最小子樹根結點的指標為a(即a是離插入點最近,且平衡因子絕對值超過1的祖先結點),則失去平衡後進行的規律可歸納為下列四種情況:
- 單向右旋平衡處理RR:由於在*a的左子樹根結點的左子樹上插入結點,*a的平衡因子由1增至2,致使以*a為根的子樹失去平衡,則需進行一次右旋轉操作;
- 單向左旋平衡處理LL:由於在*a的右子樹根結點的右子樹上插入結點,*a的平衡因子由-1變為-2,致使以*a為根的子樹失去平衡,則需進行一次左旋轉操作;
- 雙向旋轉(先左後右)平衡處理LR:由於在*a的左子樹根結點的右子樹上插入結點,*a的平衡因子由1增至2,致使以*a為根的子樹失去平衡,則需進行兩次旋轉(先左旋後右旋)操作。
- 雙向旋轉(先右後左)平衡處理RL:由於在*a的右子樹根結點的左子樹上插入結點,*a的平衡因子由-1變為-2,致使以*a為根的子樹失去平衡,則需進行兩次旋轉(先右旋後左旋)操作。
在平衡的二叉排序樹BBST (Balancing Binary Search Tree)上插入一個新的資料元素e的遞迴演算法可描述如下:
- 若BBST為空樹,則插入一個數據元素為e的新結點作為BBST的根結點,樹的深度增1;
- 若e的關鍵字和BBST的根結點的關鍵字相等,則不進行;
- 若e的關鍵字小於BBST的根結點的關鍵字,而且在BBST的左子樹中不存在和e有相同關鍵字的結點,則將e插入在BBST的左子樹上,並且當插入之後的左子樹深度增加(+1)時,分別就下列不同情況處理之:
- BBST的根結點的平衡因子為-1(右子樹的深度大於左子樹的深度,則將根結點的平衡因子更改為0,BBST的深度不變;
- BBST的根結點的平衡因子為0(左、右子樹的深度相等):則將根結點的平衡因子更改為1,BBST的深度增1;
- BBST的根結點的平衡因子為1(左子樹的深度大於右子樹的深度):則若BBST的左子樹根結點的平衡因子為1:則需進行單向右旋平衡處理,並且在右旋處理之後,將根結點和其右子樹根結點的平衡因子更改為0,樹的深度不變;
- 若e的關鍵字大於BBST的根結點的關鍵字,而且在BBST的右子樹中不存在和e有相同關鍵字的結點,則將e插入在BBST的右子樹上,並且當插入之後的右子樹深度增加(+1)時,分別就不同情況處理之。
AVL樹的旋轉
typedef struct BSTNode {
ElemType data ;
int bf ; //結點的平衡因子struct BSTNode *lchild , *rchild ; //左、右孩子指標}BSTNode , *BSTree ;
void R_Rotate (BSTree &p) {
//對以*p為根的二叉排序樹作右旋轉處理,處理之後p指向新的樹根結點,即旋轉
//處理之前的左子樹的根結點 lc=p->lchild ; //lc指向的*p的左子樹根結點 p->lchild=lc->rchild ; //lc的右子樹掛接為*p的左子樹 lc->rchild=p ; p=lc ; //p指向新的根結點}// R_Rotatevoid L_Rotate (BSTree &p) {
//對以*p為根的二叉排序樹作左旋處理,處理之後p指向新的樹根結點,即旋轉
//處理之前的右子樹的根結點 rc=p->rchild ; //rc指向的*p的右子樹根結點 p->rchild=rc->lchild ; //rc左子樹掛接為*p的右子樹 rc->lchild=p ; p=rc ; //p指向新的根結點}//L_Rotate#define LH +1 //左高#define EH 0 //等高#define RH -1 //右高
Status InsertAVL (BSTree &T , ElemType e , Boolean &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 ; return0 ; } //則不插入if (LT(e.key , T->data.key)) { //繼續在*T的左子樹中進行搜尋if (!InsertAVL(T->lchild , e , taller)) return0; //未插入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 ; break ;
}//switch(T->bf) }//ifelse { //繼續在*T的左子樹中進行搜尋if(!InsertAVL(T->rchild , e , taller)) return0 ; //未插入if (taller) //已插入到*T的右子樹中且右子樹“長高”switch ( T->bf ) { //檢查*T的平衡度case LH: //原本左子樹比右子樹高,現左、右子樹等高 T->bf=EH ; taller=FALSE ; break ;
case EH: //原本左、右子樹等高,現因右子樹增高而使樹增高 T->bf=RH ; taller=FALSE ; break ;
case RH: //原本右子樹比左子樹高,需要作右平衡處理RightBalance(T) ; taller=FALSE ; break ;
}//switch(T->bf) }//else }//elsereturn1;
}//InsertAVLvoid LeftBalance (BSTree &T) {
//對以指標T所指結點為根的二叉樹作左平衡旋轉處理,本演算法結束時,指標T指向
//新的根結點 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 ; break ;
}// switch (rd->bf) rd->bf=EH;
L_Rotate(T->lchild) ; //對*T的左子樹作左旋轉處理 R_Rotate(T) ; //對*T作右旋轉處理 }// switch (lc->bf)}//LeftBalance