天天看點

AVL樹---最簡單的實作

一、定義:

我們知道二叉查找樹,有很強的排序、查找、删除、插入的能力,但是,随着插入和删除的次數變多,二叉查找樹的工作的效率有可能的變得沒那麼好了,因為二叉查找樹的工作效率依賴于樹的高度,樹越高效率越差(同樣多的結點的情況下),是以,就需要盡可能地降低樹的高度。引入AVL樹的目的就是為了提高二叉查找樹的效率,在每次向二叉樹插入或删除結點的時候,盡可能地使二叉查找樹保持平衡,平衡狀态下樹的高度是最低的。

維基百科:

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

高度為

h

T2、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);
           

leftRotation

rightRotation

傳回調整後的根結點。

4、

RL

不平衡情況:和

LR

不平衡對稱

在原本較高的右子樹(斷句)的左子樹高度更高了導緻的不平衡,要先右單旋後左單旋。

以下假設

T1、T4

高度為

h

T2、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

,這四種情況是:

不平衡情況 說明 平衡因子特點 解決辦法
LL root的左子樹比右子樹高2,并且左子樹的左子樹比右子樹高1或相等(插入時不會,删除時可能會) 此時root的平衡因子為2,左子樹的平衡因子為1或0 右單旋
LR root的左子樹比右子樹高2,并且左子樹的右子樹比左子樹高1(其實相等也可以,但我把相等的情況放上面去了) 此時root的平衡因子為2,左子樹的平衡因子為-1 先左單旋後右單旋
RR root的右子樹比左子樹高2,并且右子樹的右子樹比左子樹高1或相等(與LL同理) 此時,root的平衡因子為-2,右子樹的平衡因子為-1或0 左單旋
RL root的右子樹比左子樹高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;
} 
           

結果圖:

AVL樹---最簡單的實作

特意找個删除後是root的左子樹的平衡結點是0(左子樹的左右子樹高度相等),當

LL

處理沒問題。

寫完了,剛了3個下午應該算搞懂了。如果哪裡錯了,還請各位指出哦!