數據結構與算法分析之AVL平衡樹


AVL樹定義:

 AVL(Adelson-Velskii和Landis)樹是帶有平衡條件的二叉查找樹。平衡條件是:其每個節點的左子樹和右子樹的高度差最多等於1的二叉查找樹。


AVL ADT操作:

Find操作:

AVL樹的查找操作跟非AVL二叉查找樹的查找是一樣的,查找最小值沿左子樹尋找,查找最大值沿右子樹查找


Insert操作:

AVL樹的插入操作除了新建節點並插入到樹中,還要爲保持樹的平衡性做調整的動作。通常通過圍繞不平衡的點進行旋轉來保持平衡性。


對於AVL樹的插入操作破壞的是從插入點到根節點路徑上的節點的平衡性。因爲只有這些節點的子樹由於插入導致平衡性的變化。所以,在插入操作時,

需要沿着插入點到根節點的路徑上更新每個節點的新高度,並檢查節點左右子樹的高度差。當發現節點左右子樹的高度差大於等於2時,說明此節點是一個

不平衡的節點,需要通過單旋轉或者雙旋轉來恢復樹的平衡性。第一個不平衡的節點也是所有不平衡節點中最深的那個。

假設節點a是一個不平衡的節點,在節點a處出現不平衡的情形有四種:

1.對a的左兒子的左子樹進行一次插入(左-左)

2.對a的左兒子的右子樹進行一次插入(左-右)

3.對a的右兒子的左子樹進行一次插入(右-左)

4.對a的右兒子的右子樹進行一次插入(右-右)

其中情形1和4是關於a點的鏡像對稱,情形2和3是關於a點的鏡像對稱。所以,理論上可以分爲兩大類,一類是插入發生在"外邊"的情況(即左-左或右-右),

此類情形可以通過圍繞a點的單旋轉來恢復平衡性;第二類是插入操作發生在內部的情形(即右-左或左-右),此類情形可以通過圍繞a點的雙旋轉來調整。


AVL樹的單旋轉:

1.左-左情形的單旋轉:

左左情形的單旋轉又可以細分成兩類,一種是a節點的左兒子沒有右兒子,另一種是a節點的左兒子沒有右兒子。所以編碼實現時要適應這兩種情況,

避免造成子樹的丟失。如下圖所示


左左情形中,要圍繞不平衡節點(K1)和K1->Left進行旋轉,當K1->Left(假定稱爲K2)有右兒子時,旋轉前值的排序肯定是K1>K2->Right>K2。所以使K1->Left=K2->Right

來避免K2的右子樹丟失,並最終返回K2的新節點。值得注意的是調整後的新節點K2與其新的父節點(上圖中的5,4)是怎樣保持鏈接的,由於插入操作的實現是遞歸方式的,先遞歸向下尋找可插入的點,插入完成後會遞歸返回,在返回時會檢查節點的平衡性,如果不平衡做旋轉操作。所以調整後新節點K2於其新的父節點的鏈接是通過對旋轉函數的調用來實現的,如:T = SingleRotateWithLeft( T );或K3->Left = SingleRotateWithRight( K3->Left );等。左單旋轉code如下所示:

static Position SingleRotateWithLeft(Position K1)
{
	Position K2;

	K2 = K1->Left;
	K1->Left = K2->Right;
	K2->Right = K1;

	K1->Height = Max(Height(K1->Left), Height(K1->Right)) + 1;
	K2->Height = Max(Height(K2->Left), K1->Height) + 1;
	//或者K2->Height = Max(Height(K2->Left), Height(K2->Right)) + 1;
	return K2;
}


2.右右情形的單旋轉:

右右情形和左左情形類似,也可以細分爲兩種情況,可以跟左左情況對比來看。如下圖所示:

右右單旋轉code如下所示:

static Position SingleRotateWithRight(Position K1)
{
	Position K2;
	K2 = K1->Right;
	K1->Right = K2->Left;
	K2->Left = K1;
	K1->Height = Max(Height(K1->Left), Height(K1->Right)) + 1;
	K2->Height = Max(Height(K2->Right), K1->Height) + 1;
	return K2;
}

左右情形:

左右情形即在節點K3的左兒子的右子樹上插入造成的。可以通過兩步單旋轉來完成所謂的雙旋轉操作:先將K3的左兒子進行一次右單旋轉,旋轉後的結果是K3左兒子的左子樹更深,所以再通過一次圍繞K3的左單旋轉最終修復K3的不平衡性。左右情形的雙旋轉如下圖所示:


左右雙旋轉的code如下所示:

static Position DoubleRotateWithLeft(Position K3)
{
	K3->Left = SingleRotateWithRight(K3->Left);
	return SingleRotateWithLeft(K3);
}

左情形的雙旋轉:

和左右情形的雙旋轉類似,右左情形是在節點K3的右兒子的左子樹上插入造成不平衡性。也可以通過兩次單旋轉來完成所謂的雙旋轉操作:先將K3的右兒子進行一次左單旋轉,然後將K3再進行一次右單旋轉。右左情形的雙旋轉示意圖如下:

右左情形的雙旋轉code實現如下:

static Position DoubleRotateWithRight(Position K3)
{
	K3->Right = SingleRotateWithLeft(K3->Right);
	return SingleRotateWithRight(K3);
}

Insert操作code實現如下:

AvlTree Insert(ElementType X, AvlTree T)
{
	if (T == NULL) {
		/* 找到插入點 */
		T = malloc(sizeof(struct AvlNode));
		if (T == NULL) {
			printf("Out of space!!!\n");
			exit(1);
		} else {
			T->Element = X;
			T->Height = 0;
			T->Left = T->Right = NULL;
		}
	} else if (X < T->Element) {	//需要在T的左子樹上插入
		T->Left = Insert(X, T->Left);
		/* 插入完成返回時檢查節點的平衡性 */
		/* 因爲是在左子樹上插入,所以左子樹高度在前減右子樹高度 */
		if (Height(T->Left) - Height(T->Right) == 2)
			if (X < T->Left->Element)
				T = SingleRotateWithLeft(T);//左左情形
			else
				T = DoubleRotateWithLeft(T);//左右情形
	} else if (X > T->Element) {	//需要在T的右子樹插入
		T->Right = Insert(X, T->Right);
		if (Height(T->Right) - Height(T->Left) == 2)
			if (X > T->Right->Element)
				T = SingleRotateWithRight(T);//右右情形
			else
				T = DoubleRotateWithRight(T);//右左情形
	}

	//更新T節點的高度
	T->Height = Max(Height(T->Left), Height(T->Right)) + 1;
	
	return T;//最終返回的是新的根節點
}


刪除操作

節點的刪除操作也會造成不平衡,根據插入操作分析的4種不平衡性,刪除操作也會造成這四種不平衡性,即LL,LR,RR,RL。相應的也要通過單旋轉或者雙旋轉來修復不平衡性。可以把刪除造成的不平衡的4種情形看作插入時造成的4種不平衡情形的鏡像。

刪除時的策略還是使用跟普通二叉查找樹一樣,將刪除節點的右子樹中最小的那個節點替換刪除節點,並遞歸的刪除替換節點,直到替換節點只有左子樹。

刪除節點後,開始遞歸的在刪除節點到根節點的路徑上檢查每個節點的平衡性,若不平衡則調整。4種不平衡的情形,在code處理時可分爲兩大類並檢查。同樣假設a節點不平衡。

1.刪除的節點在a節點的左子樹

2.刪除的節點在a節點的右子樹

進一步細分上面兩種情形:

1.刪除的節點在a節點的左子樹:

此種情形會減小a節點左子樹的高度,此時根據a節點的左兒子的左右子樹高度的差異,可以確定刪除操作對應的不平衡性是LL還是LR。

1.1如果a節點的左兒子的左子樹比右子樹高,則屬於LL類型,只需要將a節點進行左單旋轉即可。

1.2如果a節點的左兒子的左子樹比右子樹低,則屬於LR類型,只需要將a節點進行左雙旋轉即可。


2.刪除的節點在a節點的右子樹:

此種情形會減小a節點的右子樹高度,根據a節點右兒子的左右子樹高度大小,可以確定刪除操作造成的不平衡性屬於RR還是RL。

2.1如果a節點的右兒子的左子樹比右子樹低,則屬於RR類型,只需要將a節點進行右單旋轉即可。

2.2如果a節點的右兒子的左子樹比右子樹高,則屬於RL類型,只需要將a節點進行右雙旋轉即可。

無圖無真相,還是上圖吧,下圖是左子樹刪除的情形


下圖是右子樹刪除的情形:


刪除操作code實現

AvlTree Delete(ElementType X, AvlTree T)
{
	AvlTree TmpCell = NULL;

	if (T == NULL)
		return NULL;
	else if (X < T->Element) {
		/* 左子樹刪除的情形 */
		T->Left = Delete(X, T->Left);
		/* 在左子樹刪除,會造成右高左低,所以右高度減去左高度 */
		if (Height(T->Right) - Height(T->Left) == 2) {
			//T節點失去平衡
			if (Height(T->Right->Left) > Height(T->Right->Right))
				DoubleRotateWithRight(T);//RL情形
			else
				SingleRotateWithRight(T);//RR情形
		}
	} else if (X > T->Element) {
		/* 刪除的節點在T的右子樹 */
		T->Right = Delete(X, T->Right);
		/* 在右子樹刪除,會造成左高右低,所以左高度減右高度 */
		if (Height(T->Left) - Height(T->Right) == 2) {
			//T節點失去平衡
			if (Height(T->Left->Right) > Height(T->Left->Left))
				DoubleRotateWithLeft(T);//LR情形
			else
				SingleRotateWithLeft(T);//LL情形
		}
	} else if (T->Left && T->Right) {
		//找到了要刪除的節點T, 並使用T的右子樹中最小的那個替換
		TmpCell = FindMin(T->Right);
		T->Element = TmpCell->Element;
		T->Right = Delete(T->Element, T->Right);
	} else {
		//找到最後一個替換的節點,其實該節點肯定沒有左兒子
		AvlTree Position = T;
		if (T->Left)
			T = T->Left;
		if (T->Right)
			T = T->Right;
		free(Position);
	}

	if (T)
		T->Height = Max(Height(T->Left), Height(T->Right)) + 1;

	return T;
}


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