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;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值