AVL樹的定義和基本操作

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;
}
結果一樣:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章