AVL樹的構造、插入和刪除(C++)

昨天搞了一個晚上,可算是把AVL樹的概念搞明白。而後又參閱了大量的博客和書籍,終於是自己搞了一套代碼(期間各種BUG搞得我欲仙欲死= =、)雖然考研可能不要求實現,但還是寫點東西,就算是自己總結一下吧。

AVL樹是一種BST(二叉搜索樹),但是AVL是需要保持平衡的,即若AVL非空,那麼它上面每一結點的左、右子樹高度之差的絕對值不超過1,即 |hl - hr| \leq 1,其中hl - hr記做平衡因子bf


定義部分:

#include <iostream>
#include <string>
#include <cmath>
using namespace std;

//	AVL樹 結點結構體定義 
typedef int ElementType;
typedef struct TreeNode{
	ElementType val;
	TreeNode*  left;
	TreeNode* right;
	int bf;		//	平衡因子 = 左子樹高度 - 右子樹高度,只可能是-1,0,1 
	int height;	//	樹高
	//	構造函數 
	TreeNode(ElementType x):val(x), left(nullptr), right(nullptr)
		, bf(0), height(0){}			
}*AVLTree;

這就要求了AVL樹在插入、刪除時可能會造成某些結點的不平衡,此時就需要對離插入、刪除結點最近的那一個失去平衡的結點進行一些操作,使之重新達到平衡


每一次插入、刪除、旋轉都是需要更新樹高和平衡因子(這裏定義空樹高度爲-1)

int getHeight(TreeNode* t)
{
	return ( t==nullptr ? -1:t->height );
}

void reHeight_Bf(AVLTree root)
{
	//	按我們學校教材規定,空樹的樹高爲 -1	
	int hl = getHeight(root->left);
	int hr = getHeight(root->right);
	//	更新樹高、平衡因子 
	root->height =  max(hl, hr) + 1;
	root->bf = hl - hr;		 
	
}

 


 

具體的操作是什麼呢? 有四種,分別是LL, RR, LR, RL


注:四種旋轉,因爲RL和LR旋轉是基於RR和LL的,因此要按順序定義。另外需要注意的是,旋轉後的結點的高度、平衡因子均可能發生變化,記得更新(昨天被這裏坑了,調試了半天o(╯□╰)o)

LL平衡旋轉:是由於在結點A的左子樹的左子樹上插入,導致該結點平衡因子hl - hr大於1,失去平衡。此時就需要LL旋轉,具體如下:

取下A結點(即爲t),A的左子樹記爲B,B結點右子樹BR鏈接到A的左子樹,B替換A的位置,t鏈接到B的右子樹

//	rotation n. 旋轉,轉動; 輪流,循環;
void LL_rotation(AVLTree& A)
{
	AVLTree t  = A;		//保存A	 					
	AVLTree B  = A->left;	//A的左子樹 
	AVLTree BR = B->right;	//A的左子樹 的 右子樹 
	
	B->right = t;	//	B結點右子樹鏈接原來的A結點 
	t->left = BR;	//	A結點左子樹鏈接原來B結點的右子樹 
	A = B;		//	原來樹中的A結點用B結點替代 
	
	//	旋轉後結點位置變化,需要更新樹高
	reHeight_Bf(A->right);
	reHeight_Bf(A); 
}

RR平衡旋轉:與LL鏡像相反。

是由於在結點A的右子樹的右子樹上插入,導致該結點平衡因子hl - hr小於-1,失去平衡。此時就需要RR旋轉,具體如下:

取下A結點(即爲t),A的右子樹記爲B,B結點左子樹BL鏈接到A的右子樹,B替換A的位置,t鏈接到B的左子樹

 

void RR_rotation(AVLTree& A)
{
	AVLTree t  = A;		//保存A	 					
	AVLTree B  = A->right;	//A的左子樹 
	AVLTree BL = B->left;	//A的右子樹 的 左子樹 
	
	B->left = t;	//	B結點左子樹鏈接原來的A結點 
	t->right = BL;	//	A結點右子樹鏈接原來B結點的左子樹 
	A = B;		//	原來樹中的A結點用B結點替代 
	
	//	旋轉後結點位置變化,需要更新樹高
	reHeight_Bf(A->left);
	reHeight_Bf(A); 	
}

 


 LR平衡旋轉:是因爲在結點A的左子樹的右子樹上插入,導致該結點A的平衡因子hl - hr大於1,失去平衡。此時就需要1次RR操作,1次LL操作。具體如下:

①、對A的左子樹B進行RR操作;②、對A進行LL操作

void LR_rotation(AVLTree& A)
{
	RR_rotation(A->left);	//	先對A的左子樹根結點RR旋轉 
	LL_rotation(A);		//	再對A結點LL旋轉 
} 

 


 

RL平衡旋轉:與LR鏡像相反

是因爲在結點A的右子樹的左子樹上插入,導致該結點A的平衡因子hl - hr小於-1,失去平衡。此時就需要1次LL操作,1次RR操作。具體如下:

①、對A的右子樹B進行LL操作;②、對A進行RR操作

 

void RL_rotation(AVLTree& A)
{
	LL_rotation(A->right);	//	先對A的右子樹根結點LL旋轉	
	RR_rotation(A);		//	再對A結點RR旋轉 
} 

 

之前在BST那塊寫了非遞歸的插入和刪除,這裏就偷懶一哈寫遞歸的了(遞歸還是簡潔好懂啊~)

插入:

//	BST寫過非遞歸的,這裏就寫遞歸形式的好了( 其實是想偷懶了(#^.^#) ) 
bool AVL_Insert(AVLTree& root, const ElementType& x)
{
	if( !root ){
		root = new TreeNode(x);
		return true;
	}
	//	AVL樹滿足BST性質,不允許有相同的值存在 
	if( x == root->val ){
		cerr << "Same value in AVL_Tree!\n\n";
		return false;
	}
	//	左子樹遞歸 
	else if( x < root->val )
	{
		AVL_Insert(root->left, x);
		reHeight_Bf(root);    //    插入後記得更新
		if( root->bf > 1 ){
			//	結點插入到左子樹的左結點,LL 
			if( x < root->left->val ){
				cout << "value " << root->val << " LL rotation!\n";
				LL_rotation(root);
			}
			//	否則插入到左子樹的右節點,LR
			else{
				cout << "value " << root->val << " LR rotation!\n";
				LR_rotation(root);			
			}
		}			
	}
	//	右子樹遞歸		
	else if( x > root->val )
	{
		AVL_Insert(root->right, x);
		reHeight_Bf(root);    //    插入後記得更新
		if( root->bf < -1 ){
			//	結點插入到右子樹的右結點,RR 
			if( x > root->right->val ){
				cout << "value " << root->val << " RR rotation!\n";
				RR_rotation(root);
			}
			//	否則插入到右子樹的左節點,RL
			else{
				cout << "value " << root->val << " RL rotation!\n";
				RL_rotation(root);			
			}
		}			
	}
	return true;
}

刪除:

之前的搞錯了,刪除比我想象的要複雜點。大體分2種:

①、刪除後平衡因子仍在範圍內,不作處理;

②、1)刪除左子樹的結點後,若失衡,令t = 右子樹,若t的左子樹高度 > t的右子樹高度,相當於在右子樹的左子樹插入結點,執行RL操作;否則執行RR操作

        2)刪除右子樹的結點後,若失衡,令t = 左子樹,若t的左子樹高度 > t的右子樹高度,相當於在左子樹的左子樹插入結點,執行LL操作; 否則執行LR操作

//	中序遍歷下的前驅	
TreeNode* find_LeftMax(AVLTree root)
{
	TreeNode* t = root->left;	//左子樹中查找最右下結點 
	while( t->right )
		t = t->right;
	return t;	
}

//	中序遍歷下的後繼 
TreeNode* find_RightMin(AVLTree root)
{
	TreeNode* t = root->right;	//右子樹中查找最左下結點 
	while( t->left )
		t = t->left;
	return t;	
}

bool AVL_Delete(TreeNode*& p, const int& x)
{
	if( !p ){
		cerr << "No found value " << x << "\n";
		return false;
	}	
	//	查找到要刪除的結點p 
	if( x == p->val)
	{
		//	以下直接更改p的指向,是因爲傳入的參數是引用型
		//	引用型是直接對原樹的結點(而非拷貝)進行操作
		TreeNode* t = p;
		//	左、右子樹均存在 
		if( p->left && p->right){
			//	若刪除結點的 左子樹高度 > 右子樹高度
			//	找該結點的前驅 
			if( getHeight(p->left) > getHeight(p->right) ){
				t = find_LeftMax(p);
				p->val = t->val;
				AVL_Delete(p->left,  t->val);
			}
			//	否則找該結點的後繼 
			else{
				t = find_RightMin(p);
				p->val = t->val;
				AVL_Delete(p->right, t->val);
			}	
		}
		else{
			p = (p->left) ? p->left:p->right;
			delete t;
		}
	}
	//	左子樹遞歸 
	else if( x < p->val )
	{
		//	未找到刪除節點,直接返回 
		if( !AVL_Delete(p->left, x) )
			return false;
		reHeight_Bf(p);
			//	刪除左子樹結點後失去平衡 
		if( p->bf < -1 ){
			TreeNode* t = p->right;
			//	右子樹的左子樹 比 右子樹的右子樹高
			//	相當於在右子樹的左子樹插入結點 , RL 
			if( getHeight(t->left) > getHeight(t->right) ){
				cout << "value " << p->val << " RL rotation!\n";
				RL_rotation(p);
			}
			//	否則相當在右子樹的右子樹插入結點,RR
			else{
				cout << "value " << p->val << " RR rotation!\n";
				RR_rotation(p);		 
			}
		}
	}
	//	右子樹遞歸 
	else if( x > p->val )
	{
		//	未找到刪除節點,直接返回 
		if( !AVL_Delete(p->right, x) )
			return false;
		reHeight_Bf(p);	
		//	刪除右子樹結點後失去平衡 
		if( p->bf > 1 ){
			TreeNode* t = p->left;
			//	左子樹的左子樹 比 左子樹的右子樹高
			//	相當於在左子樹的左子樹插入結點 , LL 
			if( getHeight(t->left) > getHeight(t->right) ){
				cout << "value " << p->val << " LL rotation!\n";
				LL_rotation(p);
			}
			//	否則相當在左子樹的右子樹插入結點,LR
			else{
				cout << "value " << p->val << " LR rotation!\n";
				LR_rotation(p);		 
			}
		}	
	}
	return true; 
}

前、中、後序遍歷:

void visit(TreeNode* p)
{
	cout << p->val << " ";
}

//	因爲AVL樹滿足BST樹的性質,
//	即 左結點值 < 根節點值 < 右節點值 
//	因此按中序遍歷恰好可以輸出嚴格遞增序列 
void inOrder(AVLTree root)
{
	if( root ){
		inOrder(root->left);
		visit(root);
		inOrder(root->right);	
	}
}

void preOrder(AVLTree root)
{
	if( root ){
		visit(root);
		preOrder(root->left);
		preOrder(root->right);	
	}	
}

void postOrder(AVLTree root)
{
	if( root ){
		postOrder(root->left);
		postOrder(root->right);	
		visit(root);
	}	
}

測試代碼:參考了一位博客園大佬的例子(AVL樹的C測試程序,點擊左側跳轉)

int main( )
{
	AVLTree root = nullptr;
	
	int value[] = {3, 2, 1, 4, 5, 6, 7, 16, 15, 
				   14, 13, 12, 11, 10, 8, 9};	
	for(int x : value){
		cout << "Insert value " << x << ":\n";
		AVL_Insert(root, x);
		cout << "preOrder: ";
		preOrder(root);
		cout << "\n";
		cout << "inOrder: ";
		inOrder(root);
		cout << "\n\n";	
	}
	
	cout << "\n";
	preOrder(root);
	cout << "\n";	
	inOrder(root);
	cout << "\n";	
	postOrder(root);
	cout << "\n\n\n";

	int n = sizeof(value) / sizeof(value[0]);	
	while( n-- ){
		AVL_Delete(root, value[n]);
		cout << "preOrder: ";
		preOrder(root);
		cout << "\n";
		cout << "inOrder: ";
		inOrder(root);
		cout << "\n\n";
	}
	
    return 0;
}

 

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