AVL:完全平衡的二叉查找樹
二叉查找樹可以表示動態的數據集合,對於給定的數據集合,在建立一顆二叉查找樹時,二叉查找樹的結構形態與關鍵字的插入順序有關。如果全部或者部分地按照關鍵字的遞增或者遞減順序插入二叉查找樹的結點,則所建立的二叉查找樹全部或者在局部形成退化的單分支結構。在最壞的情況下,二叉查找樹可能完全偏斜,高度爲n
,其平均與最壞的情況下查找時間都是O(n)
;而最好的情況下,二叉查找樹的結點儘可能靠近根結點,其平均與最好情況的查找時間都是O(logn)。因此,我們希望最理想的狀態下是使二叉查找樹始終處於良好的結構形態。(圖片有點問題,意會就好)
1962年,Adelson-Velsikii和Landis提出了一種結點在高度上相對平衡的二叉查找樹,又稱爲AVL樹。其平均和最壞情況下的查找時間都是O(logn)
。同時,插入和刪除的時間複雜性也會保持O(logn)
,且在插入和刪除之後,在高度上仍然保持平衡。
AVL樹
又稱爲平衡二叉樹
,即Balanced Binary Tree或者Height-Balanced Tree,它或者是一棵空二叉樹,或者是具有如下性質的二叉查找樹:
- 左子樹和右子樹都是高度平衡的二叉樹;
- 左子樹和右子樹的高度之差的絕對值不超過1。
如果將二叉樹上結點的平衡因子
BF(Balanced Factor)定義爲該結點的左子樹與右子樹的高度之差,根據AVL樹的定義,AVL樹中的任意結點的平衡因子只可能是-1(右子樹高於左子樹)、0或者1(左子樹高於右子樹),在某些圖中也會表示爲絕對高度差,即0,1,2這種形式,請注意理解。
BalanceFactor = height(left-sutree) − height(right-sutree)
Rebalance:平衡調整
AVL樹的調整過程很類似於數學歸納法,每次在插入新節點之後都會找到離新插入節點最近的非平衡葉節點,然後對其進行旋轉操作以使得樹中的每個節點都處於平衡狀態。
Left Rotation:左旋,右子樹右子節點
當新插入的結點爲右子樹的右子結點時,我們需要進行左旋操作來保證此部分子樹繼續處於平衡狀態。
我們應該找到離新插入的結點最近的一個非平衡結點,來以其爲軸進行旋轉,下面看一個比較複雜的情況:
Right Rotation:右旋,左子樹左子節點
當新插入的結點爲左子樹的左子結點時,我們需要進行右旋操作來保證此部分子樹繼續處於平衡狀態。
下面看一個比較複雜的情況:
Left-Right Rotation:先左旋再右旋,左子樹右子節點
在某些情況下我們需要進行兩次旋轉操作,譬如在如下的情況下,某個結點被插入到了左子樹的右子結點:
我們首先要以A爲軸進行左旋操作,然後需要以C爲軸進行右旋操作,最終得到的又是一棵平衡樹。
下面看一個比較複雜的情況:
注意
:這裏還有另外一種情況沒有畫出來,就是新插入的節點爲45的左孩子。大家自己拿筆和紙畫一畫。
Right-Left Rotation:先右旋再左旋,右子樹左子節點
注意:這裏還有另外一種情況沒有畫出來,就是新插入的節點爲55的右孩子。大家自己拿筆和紙畫一畫。
構建平衡二叉樹的代碼實現
#include <stdio.h>
#include <stdlib.h>
//分別定義平衡因子數
#define LH +1
#define EH 0
#define RH -1
typedef int ElemType;
typedef enum {false,true} bool;
//定義二叉排序樹
typedef struct BSTNode{
ElemType data; //節點存儲的數據
int bf; //平衡標誌
struct BSTNode *lchild,*rchild;
}*BSTree,BSTNode;
//對以 p 爲根結點(以 p 爲軸)的二叉樹做右旋處理,令 p 指針指向新的樹根結點
void R_Rotate(BSTree* p)
{
// 令 p 的左孩子的右孩子作爲 p 的左孩子
BSTree lc = (*p)->lchild;
(*p)->lchild = lc->rchild;
//令 p 作爲 p 的左孩子的右孩子
lc->rchild = *p;
// 令 p 的左孩子作爲新的根節點
*p = lc;
}
//對以 p 爲根結點(以 p 爲軸)的二叉樹做左旋處理,令 p 指針指向新的樹根結點
void L_Rotate(BSTree* p)
{
// 令 p 的右孩子的左孩子作爲 p 的右孩子
BSTree rc = (*p)->rchild;
(*p)->rchild = rc->lchild;
//令 p 作爲 p 的右孩子的左孩子
rc->lchild = *p;
// 令 p 的右孩子作爲新的根節點
*p = rc;
}
//對以指針 T 所指向結點爲根結點的二叉樹作左子樹的平衡處理,令指針 T 指向新的根結點
void LeftBalance(BSTree* T)
{
BSTree lc,rd;
lc = (*T)->lchild;
//進入此方法前就已經判斷爲左旋了,還需要確定是哪種左旋:1、直接左旋,2、先左旋後右旋
//查看以 T 的左子樹爲根結點的子樹,失去平衡的原因:
// 如果 bf 值爲 1 ,則說明添加在左子樹爲根結點的左子樹中,需要對其進行右旋處理;
// 反之,如果 bf 值爲 -1,說明添加在以左子樹爲根結點的右子樹中,需要進行雙向先左旋後右旋的處理
switch (lc->bf)
{
case LH:
(*T)->bf = lc->bf = EH;
R_Rotate(T);
break;
case RH:
rd = lc->rchild;
switch(rd->bf) //這部分不好理解,拿筆在紙上畫一畫就特別好理解了(這部分的情況在前面的講解裏沒有)
{
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;
}
rd->bf = EH;
L_Rotate(&(*T)->lchild);
R_Rotate(T);
break;
}
}
//右子樹的平衡處理同左子樹的平衡處理完全類似
void RightBalance(BSTree* T)
{
BSTree lc,rd;
lc= (*T)->rchild;
switch (lc->bf)
{
case RH:
(*T)->bf = lc->bf = EH;
L_Rotate(T);
break;
case LH:
rd = lc->lchild;
switch(rd->bf)
{
case LH:
(*T)->bf = EH;
lc->bf = RH;
break;
case EH:
(*T)->bf = lc->bf = EH;
break;
case RH:
(*T)->bf = EH;
lc->bf = LH;
break;
}
rd->bf = EH;
R_Rotate(&(*T)->rchild);
L_Rotate(T);
break;
}
}
//taller 用於記錄插入新節點後是否使相應的子樹高度增加,從而根據平衡因子判斷是否需要対樹進行平衡
//算法使用了遞歸,插入完成後,逐層向上對相應子樹進行處理
//無返回值的地方,統一在最後 return 1;
int InsertAVL(BSTree* T,ElemType e,bool* taller)
{
//如果本身爲空樹(樹的根節點或相應子樹爲空),則直接添加 e 爲根結點
if ((*T)==NULL)
{
(*T)=(BSTree)malloc(sizeof(BSTNode));
(*T)->bf = EH;
(*T)->data = e;
(*T)->lchild = NULL;
(*T)->rchild = NULL;
*taller=true;
}
//如果二叉排序樹中已經存在 e ,則不做任何處理
else if (e == (*T)->data)
{
*taller = false;
return 0;
}
//如果 e 小於結點 T 的數據域,則插入到 T 的左子樹中
else if (e < (*T)->data)
{
//如果插入過程,不會影響樹本身的平衡,則直接結束
if(!InsertAVL(&(*T)->lchild,e,taller))
return 0;
//判斷插入過程是否會導致整棵樹的深度 +1
if(*taller)
{
//判斷根結點 T 的平衡因子是多少,由於是在其左子樹添加新結點的過程中導致失去平衡,
//所以當 T 結點的平衡因子本身爲 1(左高)時,需要進行左子樹的平衡處理,否則更新樹中各結點的平衡因子數
switch ((*T)->bf)
{
case LH:
LeftBalance(T); // 左旋対樹進行平衡,平衡後樹的高度不會增加
*taller = false;
break;
case EH:
(*T)->bf = LH; //更新平衡因子
*taller = true;
break;
case RH:
(*T)->bf = EH; //更新平衡因子
*taller = false;
break;
}
}
}
//同樣,當 e>T->data 時,需要插入到以 T 爲根結點的樹的右子樹中,同樣需要做和以上同樣的操作
else
{
if(!InsertAVL(&(*T)->rchild,e,taller))
return 0;
if (*taller)
{
switch ((*T)->bf)
{
case LH:
(*T)->bf = EH;
*taller = false;
break;
case EH:
(*T)->bf = RH;
*taller = true;
break;
case RH:
RightBalance(T);
*taller = false;
break;
}
}
}
return 1;
}
//判斷現有平衡二叉樹中是否已經具有數據域爲 e 的結點
bool FindNode(BSTree root,ElemType e,BSTree* pos)
{
BSTree pt = root;
(*pos) = NULL;
while(pt)
{
if (pt->data == e)
{
//找到節點,pos指向該節點並返回true
(*pos) = pt;
return true;
}
else if (pt->data>e)
{
pt = pt->lchild;
}
else
pt = pt->rchild;
}
return false;
}
//中序遍歷平衡二叉樹
void InorderTra(BSTree root)
{
if(root->lchild)
InorderTra(root->lchild);
printf("%d ",root->data);
if(root->rchild)
InorderTra(root->rchild);
}
int main()
{
int i,nArr[] = {1,23,45,34,98,9,4,35,23};
BSTree root=NULL,pos;
bool taller;
//用 nArr查找表構建平衡二叉樹(不斷插入數據的過程)
for (i=0;i<9;i++)
{
InsertAVL(&root,nArr[i],&taller);
}
//中序遍歷輸出
InorderTra(root);
//判斷平衡二叉樹中是否含有數據域爲 103 的數據
if(FindNode(root,103,&pos))
printf("\n%d\n",pos->data);
else
printf("\nNot find this Node\n");
return 0;
}