AVL樹是帶有平衡的二叉查找樹!一顆AVL樹的每一個結點的左子樹和右子樹的深度最多隻有1的差距,這就保持了這顆二叉樹的平衡!很大程度的提高了樹的使用效率!
當我們對樹進行一系列操作(插入、刪除等)後,AVL樹很可能就不能保持AVL的特性,所以在進行操作時,我們必須重新平衡這顆二叉樹!
我們把必須重新平衡的結點叫做α(這樣可以減少很多文字,又好理解),由於任意結點最多隻有兩個兒子,因此出現高度不平衡就需要α點的兩個子結點高度差大於等於2。
如意看出,這種不平衡可能出現下面四種情況:
1、對α的左子樹的左子樹進行插入;
2、對α的左子樹的右子樹進行插入;
3、對α的右子樹的右子樹進行插入;
4、對α的右子樹的左孩子進行插入。
1、3的情況是比較簡單的,進行一次旋轉就能完成平衡,而2、4的情況就比較複雜了,需要兩次旋轉!
我先定義一個AVL數的結點結構,只比查找樹多一個屬性:深度
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*結點數據域類型定義*/
typedef int ElemType;
/*AVL樹的結點聲明*/
typedef struct _AVLTNode
{
ElemType data;
struct _AVLTNode * lchild;
struct _AVLTNode * rchild;
int height; /*深度*/
}AVLTNode,*AVLTree;
還需要一個輔助的函數,幫助獲取這個深度
/*獲取樹的高度*/
static int Height(AVLTree T)
{
if(!T)
{
return -1;
}
else
{
return T->height;
}
}
/*AVL書中插入結點,插入完畢後樹依然保持AVL特性*/
AVLTree AVL_Insert(AVLTree * T, ElemType e)
{
if (*T == NULL) //如果空樹,就構造一顆根結點
{
*T = (AVLTree)malloc(sizeof(AVLTNode));
if(!*T)
{
return NULL;
}
(*T)->data = e;
(*T)->lchild = NULL;
(*T)->rchild = NULL;
(*T)->height = 0; //初始化深度爲0;
}
else if(e < (*T)->data) //左邊
{
(*T)->lchild =AVL_Insert(&(*T)->lchild,e); //遞歸左子樹插入
if (Height((*T)->lchild) - Height((*T)->rchild) == 2) //判斷左子樹和右子樹的深度差爲2 說明AVL特性被破壞,需要進行平衡
{
if (e < (*T)->lchild->data) //屬於 上面四種情況中的第一個,只需要一次旋轉
{
*T = SingleRotateLeft(*T);
}
else
{
*T = DoubleRotateLeft(*T); //第二個情況 雙旋轉
}
}
}
else if(e > (*T)->data) //反之 右面,操作和左面相反而已
{
(*T)->rchild =AVL_Insert(&(*T)->rchild,e);
if (Height((*T)->rchild) - Height((*T)->lchild) == 2)
{
if (e > (*T)->rchild->data)
{
*T = SingleRotateRight(*T);
}
else
{
*T = DoubleRotateRight(*T);
}
}
}
(*T)->height = Max(Height((*T)->rchild),Height((*T)->lchild))+1; //深度加1
return *T;
}
然後我把四種情況的看書寫出來:
/*AVL樹左單旋轉*/
static AVLTree SingleRotateLeft(AVLTree k1)
{
AVLTree k2;
k2 = k1->lchild;
k1->lchild = k2->rchild;
k2->rchild = k1;
k1->height = Max(Height(k1->rchild),Height(k1->lchild))+1;
k2->height = Max(Height(k2->rchild),Height(k2->lchild))+1;
return k2;
}
/*AVL樹右單旋轉*/
static AVLTree SingleRotateRight(AVLTree k1)
{
AVLTree k2;
k2 = k1->rchild;
k1->rchild = k2->lchild;
k2->lchild = k1;
k1->height = Max(Height(k1->rchild),Height(k1->lchild))+1;
k2->height = Max(Height(k2->rchild),Height(k2->lchild))+1;
return k2;
}
/*AVL樹左雙旋轉*/
static AVLTree DoubleRotateLeft(AVLTree k1)
{
k1 = SingleRotateRight(k1->lchild);
return SingleRotateLeft(k1);
}
/*AVL樹右雙旋轉*/
static AVLTree DoubleRotateRight(AVLTree k1)
{
k1 = SingleRotateLeft(k1->rchild);
return SingleRotateRight(k1);
}
其實就是幾個指針之間的幾個操作,比之前的鏈表的哪些操作簡單多了,初學者最好的方法就是畫圖,根據圖分析這四種情況,一目瞭然!
這裏還需要了一個Max函數,我就不貼出來了,就是比較兩個數,返回較大的數。寫不出來去小學深造去吧!
我用前序遍歷和中序遍歷做一下測試,從1打到15,順序輸入,看看輸出是什麼情況:
int main()
{
AVLTree T = NULL;
int i=0;
for (i=1;i<16;i++)
{
AVL_Insert(&T,i);
}
PreOrderTraverse(T);
printf("\n\n");
InOrderTraverse(T);
getchar();
getchar();
return 0;
}
結果如下:
第一排是前序遍歷結果,第二排是中序遍歷!!!!!剛好是一顆3層的滿二叉樹!
我再倒着15到1輸入看看結果是不是一樣:
int main()
{
AVLTree T = NULL;
int i=0;
for (i=15;i>=1;i--)
{
AVL_Insert(&T,i);
}
PreOrderTraverse(T);
printf("\n\n");
InOrderTraverse(T);
getchar();
getchar();
return 0;
}
結果一樣: