AVL树---最简单的实现

一、定义:

我们知道二叉查找树,有很强的排序、查找、删除、插入的能力,但是,随着插入和删除的次数变多,二叉查找树的工作的效率有可能的变得没那么好了,因为二叉查找树的工作效率依赖于树的高度,树越高效率越差(同样多的结点的情况下),因此,就需要尽可能地降低树的高度。引入AVL树的目的就是为了提高二叉查找树的效率,在每次向二叉树插入或删除结点的时候,尽可能地使二叉查找树保持平衡,平衡状态下树的高度是最低的。
维基百科:
在这里插入图片描述
上图给出了,AVL树的概念和平衡因子的概念(左子树的高度-右子树的高度)

二、分析:

首先空树是AVL树,当我们先AVL树插入或删除结点的AVL树可能会失去平衡,AVL树失去平衡的情况有以下四种:
在这里插入图片描述

1、左左(LL)不平衡情况:
在根结点的较高的左子树的左子树上插入了结点,使得根的平衡因子大于1而失去平衡,或者是在删除一个结点后,使得原本就更高的左子树的左边更高,这种情况要作右单旋(右边矮做右单旋)操作
以下假设T1、T2、T3是高度相同的满二叉树

			 
	     y    			     y                               x
	    / \   			    / \     Right Rotation          /  \
	   x   T3 	-------->  x   T3   - - - - - - - >        T1   y 
	  / \     	insert O  / \                             /    / \
	 T1  T2   			 T1  T2                          O    T2  T3
			    		/
			  	       O
	Node *x = y->left;
	y->left = x->right;
	x->right = y;

2、右右(RR)不平衡情况:
这个和LL对称,它是原较高的右子树的右子树更高了,这种情况要进行左单旋(左边矮做左单旋)操作
以下假设T1、T2、T3是高度相同的满二叉树

	 y                     		  x                           x                 
    / \                    		 / \             	         / \                        
   x   T3                  		T1   y     <-------        T1   y                       
  / \    \   < - - - - - - -        / \     insert O           / \                             
 T1  T2   o  Left Rotation        T2  T3                     T2  T3                                 
									    \
										 O
	Node* y = x->right;
	x->right = y->left;
	y->left = x;

3、左右(LR)不平衡情况:
原本较高的左子树(断句)的右子树高度更高了,这种情况要进行先左单,后右单的双旋操作
以下假设T1、T4高度为hT2、T3的高度h-1

     z    			       z                               z                           x
    / \   			      / \                            /   \                        /  \ 
   y   T4 	------->     y   T4  Left Rotate (y)        x    T4  Right Rotate(z)    y      z
  / \    	insert O    / \      - - - - - - - - ->    /  \      - - - - - - - ->  / \    / \
T1   x    			  T1   x                          y    T3                    T1  T2 T3  T4
    / \   			      / \                        / \						     /
  T2   T3 			    T2   T3                    T1   T2  						O
	  					/                              /
					   O						      O

上面图
假设在以z为根较高的左子树y的右子树x上插入一个结点O(x的左右子女上都可以),首先y结点的平衡因子为-2,失去平衡,这里先做个左单旋,做完后(不管做不做)z的平衡因子为2,要做个右单旋。

代码类似于:

	leftRotation(y);
	rightRotation(z);

因为y是z的左子女,所以可以写成:

	z->left = leftRotation(z->left);
	return rightRotation(z);

leftRotationrightRotation返回调整后的根结点。

4、RL不平衡情况:和LR不平衡对称
在原本较高的右子树(断句)的左子树高度更高了导致的不平衡,要先右单旋后左单旋。
以下假设T1、T4高度为hT2、T3的高度h-1

   z     					   z                            z                            x
  / \    					  / \                          / \                          /  \ 
T1   y   	----------> 	T1   y   Right Rotate (y)    T1   x      Left Rotate(z)   z      y
    / \  	insert O		    / \  - - - - - - - - ->     /  \   - - - - - - - ->  / \    / \
   x   T4					   x   T4                      T2   y                  T1  T2  T3  T4
  / \    					  / \                         /    /  \                    /
T2   T3  					T2   T3                      O    T3   T4                 O
							/
						   O			

代码类似于:

	z->left = rightRotation(z->left);
	return leftRotation(z);

在插入或删除某个结点后,只有出现了这四种情况会导致AVL树暂时失去平衡,而相应的解决办法也给出了。因此,在代码实现的时候,每次插入删除的时候,都要从插入删除位置沿双亲位置向上回溯,检查以每个双亲为根时AVL树有没有失去平衡,失去了是哪种情况就对的就用那种方法调整。所以插入删除的时候可以用递归的方法来实现,插入和删除一个结点后就回溯到上一层,检查,然后继续回溯,直到没问题的时候。

小总结:

假设定义以自底向上第一个平衡因子为2或者-2为根的树为最小不平衡AVL树。那么它不平衡的原因就四种:
设根为root,这四种情况是:

不平衡情况说明平衡因子特点解决办法
LLroot的左子树比右子树高2,并且左子树的左子树比右子树高1或相等(插入时不会,删除时可能会)此时root的平衡因子为2,左子树的平衡因子为1或0右单旋
LRroot的左子树比右子树高2,并且左子树的右子树比左子树高1(其实相等也可以,但我把相等的情况放上面去了)此时root的平衡因子为2,左子树的平衡因子为-1先左单旋后右单旋
RRroot的右子树比左子树高2,并且右子树的右子树比左子树高1或相等(与LL同理)此时,root的平衡因子为-2,右子树的平衡因子为-1或0左单旋
RLroot的右子树比左子树高2,并且右子树的左子树比右子树高1此时,root的平衡因子为-2,root的右子树平衡因子为1先右单旋后左单旋

这个表很重要,插入和删除时就是根据这个来调整不平衡的。
左边低左单旋,右边低右单旋。这个自己理解理解图就好。


三、代码实现:

1、结点结构:

class Node
{
	public:
		int key;
		int height;
		Node *left,*right;
		Node(int _key)
		{
			key = _key;
			height = 1;
			left = right = NULL;
		}
};

2、树的高度:

int height(Node *N)
{
	if(N == NULL)
	{
		return 0;
	}
	return N->height;
}

3、max函数:返回大的值

int max(int x,int y)
{
	return x > y ? x : y;
} 

4、计算以某结点为根的平衡因子:左子树的高度-右子树的高度

int getBalance(Node *root)    //获得以root为根的子树的平衡因子 
{
	if(root == NULL)
	{
		return 0;
	} 
	return height(root->left) - height(root->right);
}

5、左单旋:

Node* leftRotation(Node *x)
{//左单旋(左边矮),又称RR旋转(在较高的右子树的右子树插入结点导致失去平衡 ) 
	Node* y = x->right;
	x->right = y->left;
	y->left = x;
	
	y->height = max(height(y->left), height(y->right)) + 1;
	x->height = max(height(x->left), height(x->right)) + 1;
	
	return y;	//返回调整后的根结点 
} 

6、右单旋:

Node* rightRotation(Node *y)
{ //右单旋(右边矮),又称LL旋转(在较高的左子树的左子树插入结点导致失去平衡) 
	Node *x = y->left;
	y->left = x->right;
	x->right = y;
	
	y->height = max(height(y->left), height(y->right)) + 1;
	x->height = max(height(x->left), height(x->right)) + 1;
	
	return x; 	//返回调整后的根结点 
}

7、插入:
插入完后执行以下步骤:
1)当前节点如果是插入结点的祖先之一,那么要更新当前结点的高度。
2)获取当前结点的平衡因子(左子树的高度–右子树的高度)。
3)如果平衡因子大于1,则当前结点不平衡,我们处于“左左”情况或“左右”情况。要检查是左左情况还是左右情况,请获取左子树的平衡因子。如果左子树的平衡因子大于或等于0,则为LL情况[因为大于等于0说明左子树的左子树的高度大于或等于左子树的右子树的高度,大于0时是LL情况没问题,等于0时,为方面当作LL情况来处理(LL一次左单旋就够了,LR得先左旋后右旋)],否则为LR情况。下同
4)如果平衡因子小于-1,则当前节点不平衡,我们处于“右右”情况或“右左”情况。要检查是右右情况还是右左情况,请获取右子树的平衡因子。如果右子树的平衡因子小于或等于0,则为RR情况,否则为RL情况。

Node* insert(Node *root,int key)
{
	if(root == NULL)
	{
		return new Node(key);
	} 
	
	if(key < root->key)
	{
		root->left = insert(root->left, key); 
	}
	else if(key > root->key)
	{
		root->right = insert(root->right, key);
	}
	else 	//key == root->key不插入 
	{
		return root;
	}
	
	root->height = 1 + max(height(root->left),height(root->right));
	
	int balance = getBalance(root);		//检查平衡因子
	
	//LL情况 -->右单旋 
	if(balance > 1 && getBalance(root->left) >= 0)
	{//LL ---> 右单旋 
		return rightRotation(root); 
	} 
	
	if(balance > 1 && getBalance(root->left) < 0)
	{//LR ----->先左单旋后右单旋
		root->left = leftRotation(root->left);
		return rightRotation(root); 
	} 
	
	if(balance < -1 && getBalance(root->right) <= 0)
	{//RR---->左单旋
		return leftRotation(root);		
	}
	
	if(balance < -1 && getBalance(root->right) > 0)
	{//RL---->先右单旋后左单旋 
		root->right = rightRotation(root->right);
		return leftRotation(root);
	}
	
	return root;	//没有失去平衡 
}

8、删除:这个和二叉查找树一样,当有待删结点有两个子女时,要把它转换成删一个子女的情况(找右子树下值关键码最小的结点来代替被删(中序后继),或者左子树下关键码最大的(中序前继))。
删完后还有以下步骤:
1)当前节点如果是已删除结点的祖先之一,那么要更新当前结点的高度。
2)获取当前结点的平衡因子(左子树的高度–右子树的高度)。
3)如果平衡因子大于1,则当前结点不平衡,我们处于“左左”情况或“左右”情况。要检查是左左情况还是左右情况,请获取左子树的平衡因子。如果左子树的平衡因子大于或等于0,则为LL情况[因为大于等于0说明左子树的左子树的高度大于或等于左子树的右子树的高度,大于0时是LL情况没问题,等于0时,为方面当作LL情况来处理(LL一次左单旋就够了,LR得先左旋后右旋)],否则为LR情况。下同
4)如果平衡因子小于-1,则当前节点不平衡,我们处于“右右”情况或“右左”情况。要检查是右右情况还是右左情况,请获取右子树的平衡因子。如果右子树的平衡因子小于或等于0,则为RR情况,否则为RL情况。

Node *minValueNode(Node *node)
{//中序右子树下第一个结点(右子树下key值最小) 
	Node *current = node;
	
	while(current->left != NULL)
	{
		current = current->left;
	}
	return current;
}
Node* deleteNode(Node* root,int key)
{
	if(root == NULL)
	{
		return root;
	} 

	if(key < root->key)
	{
		root->left = deleteNode(root->left, key);
	}
	else if(key > root->key)
	{
		root->right = deleteNode(root->right, key); 
	}
	else
	{
		if((root->left == NULL) || (root->right == NULL))
		{
			Node* temp = root->left?
						 root->left:
						 root->right; 
			if(temp == NULL)  //如果没有子女 
			{
				temp = root;	//temp 保存要删除结点 
				root = NULL;    // root 置空 
			}
			else		//有一个子女 
			{
				*root = *temp;		//把temp的值拷给root,temp代替被删 
			}
			delete temp; 
		}
		else    //如果有两个子女-->找个只有一个子女或者没有子女的子孙, 
		{		//把它的值存到当前结点,然后把这个子孙删了 
			Node *temp = minValueNode(root->right);
			
			root->key = temp->key;
			
			root->right = deleteNode(root->right,
									 temp->key);
		} 
	}
	
	//删完后root == NULL就不用检查以root为根的子树平不平衡了
	if(root == NULL)
	{
		return root;
	} 
	
	root->height = 1 + max(height(root->left),
						   height(root->right));
	
	int balance = getBalance(root);
	
	if(balance > 1 && getBalance(root->left) >= 0)
	{//LL ---> 右单旋 
		return rightRotation(root); 
	} 
	
	if(balance > 1 && getBalance(root->left) < 0)
	{//LR ----->先左单旋后右单旋
		root->left = leftRotation(root->left);
		return rightRotation(root); 
	} 
	
	if(balance < -1 && getBalance(root->right) <= 0)
	{//RR---->左单旋
		return leftRotation(root);		
	}
	
	if(balance < -1 && getBalance(root->right) > 0)
	{//RL---->先右单旋后左单旋 
		root->right = rightRotation(root->right);
		return leftRotation(root);
	}
	return root;		//返回删完后调整后的根
}

9、测试代码:

#include<iostream>
#include<vector>
#include<string>

using namespace std;

class Node
{
	public:
		int key;
		int height;
		Node *left,*right;
		Node(int _key)
		{
			key = _key;
			height = 1;
			left = right = NULL;
		}
};
int height(Node *N)
{
	if(N == NULL)
	{
		return 0;
	}
	return N->height;
}
int max(int x,int y)
{
	return (x > y )? x : y;
} 
int getBalance(Node *root)    //获得以root为根的子树的平衡因子 
{
	if(root == NULL)
	{
		return 0;
	} 
	return height(root->left) - height(root->right);
}
/*
     y                               x
    / \     Right Rotation          /  \
   x   T3   - - - - - - - >        T1   y 
  / \       < - - - - - - -            / \
 T1  T2     Left Rotation            T2  T3
*/
Node* leftRotation(Node *x)
{//左单旋(左边矮),又称RR旋转(在较高的右子树的右子树插入结点导致失去平衡 ) 
	Node* y = x->right;
	x->right = y->left;
	y->left = x;
	
	//更新x,y的高度,必须先x,后y,因为x是y的子树 
	x->height = max(height(x->left), height(x->right)) + 1;
	y->height = max(height(y->left), height(y->right)) + 1;
	
	return y;	//返回调整后的根结点 
} 
Node* rightRotation(Node *y)
{ //右单旋(右边矮),又称LL旋转(在较高的左子树的左子树插入结点导致失去平衡) 
	Node *x = y->left;
	y->left = x->right;
	x->right = y;
	
	//更新x,y的高度,必须先y,后x,因为y是x的子树 
	y->height = max(height(y->left), height(y->right)) + 1;
	x->height = max(height(x->left), height(x->right)) + 1;
	
	return x; 	//返回调整后的根结点 
} 
Node* insert(Node *root,int key)
{
	if(root == NULL)
	{
		return new Node(key);
	} 
	
	if(key < root->key)
	{
		root->left = insert(root->left, key); 
	}
	else if(key > root->key)
	{
		root->right = insert(root->right, key);
	}
	else 	//key == root->key不插入 
	{
		return root;
	}
	
	root->height = 1 + max(height(root->left),height(root->right));
	
	int balance = getBalance(root);		//检查平衡因子
	
	//LL情况 -->右单旋 
	if(balance > 1 && getBalance(root->left) >= 0)
	{//LL ---> 右单旋 
		return rightRotation(root); 
	} 
	
	if(balance > 1 && getBalance(root->left) < 0)
	{//LR ----->先左单旋后右单旋
		root->left = leftRotation(root->left);
		return rightRotation(root); 
	} 
	
	if(balance < -1 && getBalance(root->right) <= 0)
	{//RR---->左单旋
		return leftRotation(root);		
	}
	
	if(balance < -1 && getBalance(root->right) > 0)
	{//RL---->先右单旋后左单旋 
		root->right = rightRotation(root->right);
		return leftRotation(root);
	}
	
	return root;	//没有失去平衡 
}
Node *minValueNode(Node *node)
{//中序右子树下第一个结点(右子树下key值最小) 
	Node *current = node;
	
	while(current->left != NULL)
	{
		current = current->left;
	}
	return current;
}

Node* deleteNode(Node* root,int key)
{
	if(root == NULL)
	{
		return root;
	} 
	
	if(key < root->key)
	{
		root->left = deleteNode(root->left, key);
	}
	else if(key > root->key)
	{
		root->right = deleteNode(root->right, key); 
	}
	else
	{
		if((root->left == NULL) || (root->right == NULL))
		{
			Node* temp = root->left?
						 root->left:
						 root->right; 
			if(temp == NULL)  //如果没有子女 
			{
				temp = root;	//temp 保存要删除结点 
				root = NULL;    // root 置空 
			}
			else		//有一个子女 
			{
				*root = *temp;		//把temp的值拷给root,temp代替被删 
			}
			delete temp; 
		}
		else    //如果有两个子女-->找个只有一个子女或者没有子女的子孙, 
		{		//把它的值存到当前结点,然后把这个子孙删了 
			Node *temp = minValueNode(root->right);
			
			root->key = temp->key;
			
			root->right = deleteNode(root->right,
									 temp->key);
		} 
		
	}
	
	//删完后root == NULL就不用检查以root为根的子树平不平衡了
	if(root == NULL)
	{
		return root;
	} 
	
	root->height = 1 + max(height(root->left),
						   height(root->right));
	
	int balance = getBalance(root);
	
	if(balance > 1 && getBalance(root->left) >= 0)
	{//LL ---> 右单旋 
		return rightRotation(root); 
	} 
	
	if(balance > 1 && getBalance(root->left) < 0)
	{//LR ----->先左单旋后右单旋
		root->left = leftRotation(root->left);
		return rightRotation(root); 
	} 
	
	if(balance < -1 && getBalance(root->right) <= 0)
	{//RR---->左单旋
		return leftRotation(root);		
	}
	
	if(balance < -1 && getBalance(root->right) > 0)
	{//RL---->先右单旋后左单旋 
		root->right = rightRotation(root->right);
		return leftRotation(root);
	}
	
	return root;
	
}
void printBST(Node *root, int k)
{
	if(root != NULL)
	{
		printBST(root->right,k+5);
		for(int i = 0; i < k; i++)
		{
			cout<<" ";
		}
		cout<<root->key<<endl;
		printBST(root->left,k+5);
	}
}
int main()
{
	Node *root = NULL;
	root = insert(root,10); 
	root = insert(root,5); 
	root = insert(root,11); 
	root = insert(root,3); 
	root = insert(root,6);
	root = insert(root,12); 
	root = insert(root,2); 
	root = insert(root,9);  	  
	printBST(root,0);
	cout<<"------LL---------"<<endl;
	root = deleteNode(root,12);
	printBST(root,0);	
	return 0;
} 

结果图:
在这里插入图片描述
特意找个删除后是root的左子树的平衡结点是0(左子树的左右子树高度相等),当LL处理没问题。


写完了,刚了3个下午应该算搞懂了。如果哪里错了,还请各位指出哦!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值