AVL樹維基百科:http://zh.wikipedia.org/wiki/AVL樹
在計算機科學中,AVL樹是最先發明的自平衡二叉查找樹。在AVL樹中任何節點的兩個子樹的高度最大差別爲1,所以它也被稱爲高度平衡樹。查找、插入和刪除在平均和最壞情況下都是O(log n)。增加和刪除可能需要通過一次或多次樹旋轉來重新平衡這個樹。AVL樹得名於它的發明者G.M. Adelson-Velsky和E.M. Landis,他們在1962年的論文《An algorithm for the organization of information》中發表了它。
原理請看上面維基百科詞條,可以參考嚴蔚敏數據結構或其它書籍,這裏就不對原理做過多解釋了,下面將直接給出其實現,代碼有詳細註釋。
1、基本約定
使用平衡二叉樹就是爲了高效的查找,一般是根據關鍵字查找記錄,而記錄一般是複雜的類型對象。這裏我們以一個Student類作爲記錄類型,學號作爲關鍵字。
我們假定所使用的元素類型,都能進行各種比較和賦值。用LH,EH,RH分別表示左子樹高,等高,右子樹高,即平衡因子-1、0、1。
#define LH +1 //左高
#define EH 0 //等高
#define RH -1 //右高
#define EQ(a,b) ((a) == (b))
#define LT(a,b) ((a) < (b))
#define LQ(a,b) ((a) <= (b))
//結點元素類型
typedef struct Student
{
int key;
string major;
Student(){}
Student(int k,string s) : key(k), major(s){}
}ElementType;
ostream& operator<<(ostream& out, const Student& s)
{
out<<"("<<s.key<<","<<s.major<<")";
return out;
}
istream& operator>>(istream& in,Student& s)
{
in>>s.key>>s.major;
}
typedef int KeyType;//關鍵字類型
typedef struct AVLNode
{
ElementType data;
int bf;
struct AVLNode* lchild;
struct AVLNode* rchild;
AVLNode(){}
AVLNode(ElementType& e, int ibf=EH, AVLNode* lc=NULL, AVLNode* rc=NULL)
: data(e), bf(ibf), lchild(lc),rchild(rc){}
}AVLNode, *AVL;
2、初始化、銷燬
/*
*Description: 初始化(其實可以不用)
*/
void initAVL(AVL& t)
{
t = NULL;
}
/*
*Description: 銷燬平衡二叉樹
*/
void destroyAVL(AVL& t)
{
if(t)
{
destroyAVL(t->lchild);
destroyAVL(t->rchild);
delete t;
t = NULL;
}
}
3、遍歷和查找//前序遍歷
void preOrderTraverse(AVL t)
{
if(t)
{
cout<<t->data<<" ";
preOrderTraverse(t->lchild);
preOrderTraverse(t->rchild);
}
}
//中序遍歷
void inOrderTraverse(AVL t)
{
if(t)
{
inOrderTraverse(t->lchild);
cout<<t->data<<" ";
inOrderTraverse(t->rchild);
}
}
//以前序和中序輸出平衡二叉樹
void printAVL(AVL t)
{
cout<<"inOrder: "<<endl;
inOrderTraverse(t);
cout<<endl;
cout<<"preOrder: "<<endl;
preOrderTraverse(t);
cout<<endl;
}
/*
Description:
在根指針t所指平衡二叉樹中遞歸地查找某關鍵字等於key的數據元素,
若查找成功,則返回指向該數據元素結點的指針,否則返回空指針。
根據需要,也可以返回一個bool值
*/
AVLNode* searchAVL(AVL& t, KeyType key)
{
if((t == NULL)||EQ(key,t->data.key))
return t;
else if LT(key,t->data.key) /* 在左子樹中繼續查找 */
return searchAVL(t->lchild,key);
else
return searchAVL(t->rchild,key); /* 在右子樹中繼續查找 */
}
4、旋轉處理
左旋和右旋,大家記住“左逆右順”就可以了。
(1)左旋-逆時針旋轉(如RR型就得對根結點做該旋轉)
/*
Description:
對以*p爲根的二叉排序樹作左旋處理,處理之後p指向新的樹根結點,即旋轉
處理之前的右子樹的根結點。也就是書上說說的RR型.
*/
void L_Rotate(AVLNode* &p)
{
AVLNode * rc = NULL;
rc = p->rchild; //rc指向p的右子樹根結點
p->rchild = rc->lchild;//rc的左子樹掛接爲p的右子樹
rc->lchild = p;
p = rc; //p指向新的根結點
}
(2)右旋-順時針旋轉(如LL型就得對根結點做該旋轉)
/*
Description:
對以*p爲根的二叉排序樹作右旋處理,處理之後p指向新的樹根結點,即旋轉
處理之前的左子樹的根結點。也就是書上說說的LL型.
*/
void R_Rotate(AVLNode* &p)
{
AVLNode * lc = NULL;
lc = p->lchild; //lc指向p的左子樹根結點
p->lchild = lc->rchild; //lc的右子樹掛接爲p的左子樹
lc->rchild = p;
p = lc; //p指向新的根結點
}
5、左平衡處理
所謂左平衡處理,就是某一根結點的左子樹比右子樹過高,從而失去了平衡。
(1)插入時如果需要左平衡處理,根結點左子樹根平衡因子只可能爲LH和RH。
(2)刪除和插入不同,根結點左子樹根的平衡因子三種情況都可能出現,因爲是刪除根結點右子樹中的結點從而引起左子樹過高,在刪除前,根結點左子樹根的平衡因子是可以爲EH的,此種情況同樣是對根結點做簡單右旋處理。
/*對以指針t所指結點爲根的二叉樹作左平衡旋轉處理
包含LL旋轉和LR旋轉兩種情況
平衡因子的改變其實很簡單,自己畫圖就出來了
*/
void leftBalance(AVLNode* &t)
{
AVLNode* lc = NULL;
AVLNode* rd = NULL;
lc = t->lchild;
switch(lc->bf)
{
case LH: //LL旋轉
t->bf = EH;
lc->bf = EH;
R_Rotate(t);
break;
case EH: //deleteAVL需要,insertAVL用不着
t->bf = LH;
lc->bf = RH;
R_Rotate(t);
break;
case RH: //LR旋轉
rd = lc->rchild;
switch(rd->bf)
{
case LH:
t->bf = RH;
lc->bf = EH;
break;
case EH:
t->bf = EH;
lc->bf = EH;
break;
case RH:
t->bf = EH;
lc->bf = LH;
break;
}
rd->bf = EH;
L_Rotate(t->lchild);//不能寫L_Rotate(lc);採用的是引用參數
R_Rotate(t);
break;
}
}
6、右平衡處理
類似左平衡處理,所謂右平衡處理,就是某一根結點的右子樹比左子樹過高,從而失去了平衡。
(1)插入時如果需要右平衡處理,根結點右子樹根平衡因子只可能爲LH和RH。
(2)刪除和插入不同,根結點右子樹根的平衡因子三種情況都可能出現,因爲是刪除根結點左子樹中的結點從而引起右子樹過高,在刪除前,根結點右子樹根的平衡因子是可以爲EH的,此種情況同樣是對根結點做簡單左旋處理。
/*對以指針t所指結點爲根的二叉樹作右平衡旋轉處理
包含RR旋轉和RL旋轉兩種情況
*/
void rightBalance(AVLNode* &t)
{
AVLNode* rc = NULL;
AVLNode *ld = NULL;
rc = t->rchild;
switch(rc->bf)
{
case LH: //RL旋轉
ld = rc->lchild;
switch(ld->bf)
{
case LH:
t->bf = EH;
rc->bf = RH;
break;
case EH:
t->bf = EH;
rc->bf = EH;
break;
case RH:
t->bf = LH;
rc->bf = EH;
break;
}
ld->bf = EH;
R_Rotate(t->rchild);//不能寫R_Rotate(rc);採用的是引用參數
L_Rotate(t);
break;
case EH: //deleteAVL需要,insertAVL用不着
t->bf = RH;
rc->bf = LH;
L_Rotate(t);
break;
case RH: //RR旋轉
t->bf = EH;
rc->bf = EH;
L_Rotate(t);
break;
}
}
7、插入處理
在插入一個元素時,總是插入在一個葉子結點上。我們採用遞歸插入,也就是不斷搜索平衡二叉樹,找到一個合適的插入點(當然相同關鍵字不插入)。插入後,引起的第一個不平衡的子樹的根結點,一定是在查找路徑上離該插入點最近的,注意看代碼中遞歸後的回溯。
/*
若在平衡的二叉排序樹t中不存在和e有相同關鍵字的結點,則插入一個
數據元素爲e的新結點,並返回true,否則返回false。若因插入而使二叉排序樹
失去平衡,則作平衡旋轉處理,布爾變量taller反映t長高與否
*/
bool insertAVL(AVL& t, ElementType& e, bool& taller)
{
if(t == NULL)
{
t = new AVLNode(e); //插入元素
taller = true;
}
else
{
if(EQ(e.key, t->data.key)) //樹中已含該關鍵字,不插入
{
taller = false;
return false;
}
else if(LT(e.key, t->data.key))//在左子樹中查找插入點
{
if(!insertAVL(t->lchild, e, taller))//左子樹插入失敗
{
return false;
}
if(taller) //左子樹插入成功,且左子樹增高
{
switch(t->bf)
{
case LH: //原來t的左子樹高於右子樹
leftBalance(t); //做左平衡處理
taller = false;
break;
case EH: //原來t的左子樹和右子樹等高
t->bf = LH; //現在左子樹比右子樹高
taller = true; //整棵樹增高了
break;
case RH: //原來t的右子樹高於左子樹
t->bf = EH; //現在左右子樹等高
taller = false;
break;
}
}
}
else //在右子樹中查找插入點
{
if(!insertAVL(t->rchild, e, taller))//右子樹插入失敗
{
return false;
}
if(taller) //右子樹插入成功,且右子樹增高
{
switch(t->bf)
{
case LH: //原來t的左子樹高於右子樹
t->bf = EH;
taller = false;
break;
case EH: //原來t的左子樹和右子樹等高
t->bf = RH;
taller = true;
break;
case RH: //原來t的右子樹高於左子樹
rightBalance(t);//做右平衡處理
taller = false;
break;
}
}
}
}
return true; //插入成功
}
8、刪除處理
刪除和插入不同的是,刪除的結點不一定是葉子結點,可能是樹中的任何一個結點。前面在講解二叉查找樹時,我們知道刪除的結點可能有三種情況:(1)爲葉子結點,(2)左子樹或右子樹有一個爲空,(3)左右子樹都不空。對第三種情況的處理我們介紹了三種處理方式,這裏我們採用刪除前驅的方式。注意到我們仍然採用的是遞歸刪除,然後判斷刪除後樹是否“變矮”了,然後進行相應的處理。對(1)(2)中情況,很好處理,樹的確是“變矮”了。對於第(3)種情況,我們不能直接找到前驅結點,然後把數據拷貝到原本要刪除的根結點,最後直接刪除前驅結點。因爲這麼做,我們無法判斷原先根結點子樹高度的變化情況。所以我們在找到前驅結點後,不是直接刪除,而是採用在根結點左子樹中遞歸刪除前驅的方式。
/*
若在平衡的二叉排序樹t中存在和e有相同關鍵字的結點,則刪除之
並返回true,否則返回false。若因刪除而使二叉排序樹
失去平衡,則作平衡旋轉處理,布爾變量shorter反映t變矮與否
*/
bool deleteAVL(AVL& t, KeyType key, bool& shorter)
{
if(t == NULL) //不存在該元素
{
return false; //刪除失敗
}
else if(EQ(key, t->data.key)) //找到元素結點
{
AVLNode* q = NULL;
if(t->lchild == NULL) //左子樹爲空
{
q = t;
t = t->rchild;
delete q;
shorter = true;
}
else if(t->rchild == NULL) //右子樹爲空
{
q = t;
t = t->lchild;
delete q;
shorter = true;
}
else //左右子樹都存在,
{
q = t->lchild;
while(q->rchild)
{
q = q->rchild;
}
t->data = q->data;
deleteAVL(t->lchild, q->data.key, shorter); //在左子樹中遞歸刪除前驅結點
}
}
else if(LT(key, t->data.key)) //左子樹中繼續查找
{
if(!deleteAVL(t->lchild, key, shorter))
{
return false;
}
if(shorter)
{
switch(t->bf)
{
case LH:
t->bf = EH;
shorter = true;
break;
case EH:
t->bf = RH;
shorter = false;
break;
case RH:
rightBalance(t); //右平衡處理
if(t->rchild->bf == EH)//注意這裏,畫圖思考一下
shorter = false;
else
shorter = true;
break;
}
}
}
else //右子樹中繼續查找
{
if(!deleteAVL(t->rchild, key, shorter))
{
return false;
}
if(shorter)
{
switch(t->bf)
{
case LH:
leftBalance(t); //左平衡處理
if(t->lchild->bf == EH)//注意這裏,畫圖思考一下
shorter = false;
else
shorter = true;
break;
case EH:
t->bf = LH;
shorter = false;
break;
case RH:
t->bf = EH;
shorter = true;
break;
}
}
}
return true;
}
注:
(1)在平衡二叉樹(AVL)插入和刪除詳解(下)中給出測試代碼和測試用例,並給出了一個涵蓋所有情況的圖例。
平衡二叉樹的插入和刪除詳解(下): http://blog.csdn.net/sysu_arui/article/details/7906303
(2)也許大家難以理解的是插入和刪除過程中,平衡因子的變化。其實,插入和刪除都是遞歸進行的,平衡因子的變化是在遞歸回溯過程中,自底向上改變的,至於怎麼改變,把幾種情況弄清楚之後,畫圖就出來了。
(3)另一種簡單的C++實現,不用計算平衡因子:http://blog.csdn.net/sysu_arui/article/details/7921498
(4)建議看看二叉查找樹的插入和刪除,對比分析一下:http://blog.csdn.net/sysu_arui/article/details/7865864
參考資料:
[1]嚴蔚敏 數據結構(C語言版)
[2]一篇博客文章:http://blog.sina.com.cn/s/blog_66aeefeb010127ht.html (注意文章中,插入操作有點錯誤)
[3]另一種C實現:http://ishare.iask.sina.com.cn/f/25570459.html?retcode=0