AVL樹詳解(附c++程式碼)
1.AVL樹簡介
AVL樹是一種自平衡的二叉搜尋樹,一顆典型的二叉搜尋樹(Binary Search Tree,以下簡稱BST)應該滿足Key(left) < Key(root) < Key(right)。即左子樹上所有的鍵值都小於根節點的鍵值,根節點的鍵值小於所有右子樹上的鍵值(左右子樹都不為空的話)。也正是因為這種特性,如果對於一般的BST不做任何平衡操作的話,在構建BST的時候左右子樹的高度會出現嚴重不均衡的情況,例如:按照升序(降序)輸入序列會造成最極端的不平衡情況。
圖1 升序構建的BST 圖2 降序構建的BST 圖3 平衡的BST
1.1一些概念
為了解決這種情況,平衡的BST概念就應運而生了。其中AVL樹是最先發明的自平衡二叉搜尋樹。自平衡的概念就是在插入、刪除的過程中,樹會自動平衡自身。在這裡我們需要規定一下平衡的概念。一顆平衡二叉樹,它的左右子樹的高度差不能超過1,這個性質應用到這顆樹的任意一個節點都要成立。樹的高度的概念:從根節點出發,尋找到離它最遠的葉節點所經過的路徑,如果一個節點為空,它的高度為-1。高度的概念也可以應用到每一個節點。對於圖1,節點1的高度為2,節點2的高度為1,節點3的高度為0.對於圖3,節點1和節點3的高度都為0,節點2的高度為1。由此我們又可以引申出節點的平衡因子這個概念。一個節點的平衡因子是:height(x->left)-height(x->right),這裡就不再舉例細說了。
2.具體操作
2.1左旋、右旋、平衡
AVL樹最重要的操作其實說穿了只有兩個:左旋和右旋。我們所說的左旋右旋是針對某個節點而言。對於上文圖1,將節點1向左旋轉就形成了圖3,對於圖2 ,將節點3進行右旋就形成了圖3。當某一個節點的平衡因子大於1時,要將該節點進行右旋;當某一節點的平衡因子小於-1時,要將該節點進行左旋。這裡說法不夠嚴謹,具體細節我們到平衡操作時再細說。平衡因子大於1,說明左子樹高度至少比右子樹高度大2,小於-1,右子樹高度比左子樹至少大2。具體旋轉應該怎麼操作呢,我們來看一個例子。
圖4 左子樹高的不平衡BST
圖5 右旋後平衡的BST
對比上面兩幅圖,我們發現不平衡發生在節點x,它的平衡因子為2,根據上文所說,那麼從大方向上而言,它需要進行右旋。右旋過後如圖5所示,我們發現,前後兩顆樹,只有被標記的三個節點的相對位置發生了變化,其他節點相對位置沒有變化。用程式碼來表示就是
/*經由下面的3個步驟,原先x的左節點變為了新樹的根節點,原先的x節點變為了新樹左節點,原先x左節點的右節點變成了x節點的左孩子,還請好好理解*/
Node* t = x->left;
x->left = t->right;
t->right = x;
右旋操作就是這樣,左旋操作很類似,我們以同樣的方式來舉例說明。
圖6 右子樹高的不平衡BST(圖畫的有點問題,x的balance=-2,x->right的balance=-1)
圖7 左旋後平衡的BST
同樣的,左旋時,相對位置發生變化的也只有被標記出來的3個節點。所以用程式碼來表示就是
Node* t = x->right;
x->right = t->left;
t->left = x;
和右旋的程式碼結構一模一樣,只不過是對稱了一下而已。
理解了左旋和右旋,那我們再來理解平衡就簡單很多了。大多數講解AVL樹平衡的資料都講平衡(旋轉)操作分為了4類,LL型,LR型,RL型,RR型。我這裡只想分為左旋和右旋兩大類來介紹。其實和那4類是一樣的。
第一大類:右旋
根據我們上文所講,當一個節點的平衡因子大於1的時候,它需要進行右旋才能平衡。但是我們沒有考慮該節點(x)的左孩子節點(x->left)的平衡因子,如果balanceFactor(x->left) > 0,那麼這就對應著圖4 所描述的情況,這種情況下,我們只需將x節點進行一次右旋就能得到平衡的BST了。
那如果balanceFactor(x->left) < 0呢,這時候只對x節點進行一次右旋就能平衡了嗎?我們不妨來看一看。
圖8 balanceFactor(x->left) < 0的不平衡BST
對於這樣的不平衡二叉樹,如果只對x節點進行一次右旋將會導致什麼結果呢?試一試
圖9 只進行一次右旋後形成的BST
只通過一次右旋,我們可以很清楚地看到新的樹仍然是不平衡的,新樹的根節點的balance = -2。可能有人有疑問了,因為我們上文提到過,當一個節點的平衡因子小於-1時,需要對它進行左旋才能平衡,那麼對於這樣的一棵樹再進行一次左旋能否平衡呢?答案是不能,因為如果再將新的根節點進行左旋,就會得到原先的不平衡的BST,還是無法平衡。這顆樹的平衡操作我們在討論左旋時再具體解釋,現在我們著重關心一下,對於圖8所示的不平衡的BST,如何讓它平衡呢?
正確的操作應該是先將x->left進行一次左旋,這樣就形成了如同圖4一樣的BST,之後再將x節點進行一次右旋,這就平衡了。我們一步一步來看看,首先將x->left進行一次左旋。
圖10 先進行一次左旋後形成的BST
然後再將根節點進行一次右旋
圖11 第二次進行右旋後形成的平衡的BST
至此,關於右旋的平衡我們就討論完了。
第二大類:左旋
有了右旋平衡的基礎,再討論左旋平衡思路就清晰很多了。
同理,我們提到過如果一個節點的平衡因子小於-1,就應該對它進行左旋,但是我們沒有考慮該節點(x)的右孩子節點(x->right)的平衡因子,如果balanceFactor(x->right) < 0,這種情況對應的就是圖6所描述的情況,我們只需要進行一次左旋即可。
如果balanceFactor(x->right) > 0呢?這種情況就是形同圖9所示的樹。根據我們對右旋平衡的討論,我們可以很容易地推測出,如果再這種情況下還是隻進行一次左旋操作的話,將得到形同圖8所示的樹。
正確的操作應該是先將x->right進行一次右旋,然後對新樹的根節點進行一次左旋就能得到一顆平衡的BST了。這裡我就不給出具體的圖示了,如果大家能夠理解關於右旋平衡的討論,那麼左旋平衡應該不難自己畫圖理解。
如果我們事先已經實現了左旋和右旋的函式(實際的程式碼中確實會事先實現這兩個函式),那麼我們的balance函式可以這麼寫:
Node* balance(Node* x){
if(balanceFactor(x) < -1){ //balanceFactor函式用來取得一個節點的平衡因子
if(balanceFactor(x->right) > 0){
x->right = rotateRight(x->right);
}
x = rotateLeft(x);
}
else if(balanceFactor(x) > 1){
if(balanceFactor(x->left) < 0){
x->left = rotateLeft(x->left);
}
x = rotateRight(x);
}
return x;
}
至此,平衡的有關操作討論完了,我們再來關注AVL樹中最複雜的兩個操作,插入(put)和刪除(delete)。
3.插入
有了上述關於平衡的理解,再談插入就很簡單了。我們put函式是這樣宣告的。Node* put(Node* x, Key key,Value val);當新插入一個鍵值對時,會和當前節點比較。如果要插入的鍵值較小,就遞迴地在左子樹中插入;如果較大,就遞迴地在右子樹中插入;如果相等,就直接改變當前節點的val即可;如果當前節點為空,直接返回一個新節點;最後呼叫balance函式進行平衡。忘了說一句,我所實現的AVL樹的節點是儲存的一對鍵值對,用鍵值來比較。由於遞迴,就實現了AVL樹自平衡性的性質。
圖12 一個簡單的插入例子
程式碼表示
Nodee* put(Node* x, Key key, Value val){
<span style="font-size: 12px;"> //如果為空,直接建立一個新節點返回</span>
if(x == NULL) return new Node<Key,Value,Compare>(key,val,0,1);
if(key < x->key) x->left = put(x->left,key,val); //遞迴
else if(x->key < key) x->right = put(x->right,key,val); //遞迴
else {x->val = val; return x;}
<span style="white-space:pre"> </span>//更新大小和高度
x->N = 1+size(x->left) + size(x->right);
x->h = 1+max(height(x->left),height(x->right));
return balance(x); //平衡
}
4.刪除
刪除的情況要稍微複雜一點。不過如果瞭解二叉搜尋樹的刪除原理,那麼AVL樹的刪除也就不難理解了,唯一的區別就是多了一個平衡操作。如果要刪除的節點只有一個子節點或者沒有子節點,那麼這種情況是很簡單的,直接返回該節點的另一個子節點就行(如果沒有子節點就直接返回了NULL)。現在假定我們有如下所示的一顆AVL樹。
圖13 一顆AVL樹
1.假定我們要刪除鍵值為A或者鍵值為E的節點,這兩個節點的共同之處在於它們都只有一個子節點。要刪除它們很簡單,只需要將原先指向它們的連線重新指向它們的另一個非空的孩子節點即可。(葉節點的刪除包含在這兩種情況中了,不再單獨討論)如下圖
圖14 刪除兩個節點後的AVL樹
原理很簡單,程式碼實現也很簡單,刪除後再進行balance操作,對於這個例子,刪除後仍然是AVL樹,所以不用平衡了。
2.如果我們刪除的節點包含兩個子節點那該怎麼辦呢?例如,要刪除鍵值為I的節點。其實這個二叉搜尋樹的刪除方法一樣,我們在要刪除的節點的右孩子節點中尋找到一個在右子樹中鍵值最小的節點,把該節點用最小的那個節點替代。在這個例子中,就是把鍵值為I的節點用鍵值為J 的節點替代。這樣做的原因是要維護樹的有序性。因為是在右子樹中找的節點,所以這個節點肯定比原先節點的左子樹中的所有的節點的鍵值都要大,又因為是右子樹中鍵值最小的節點,所以剩下的右子樹的所有節點的鍵值都比這個節點的鍵值要大。這個應該不難理解。尋找到最小節點很容易,那麼怎麼刪除呢?其實刪除最小節點可以歸類到第一種情況中,因為含有最小鍵值的節點最多隻有一個右孩子節點(原因大家可以花幾秒鐘考慮一下)。所以這就相當於刪除一個葉節點或者刪除一個只含有右孩子節點的節點。在第一種情況中我們已經分析過了。刪除後的樹如下圖:
圖15 再刪除一個含有兩個子節點的節點
最後不要忘了對該節點進行balance操作,我們給的例子刪除後不需要balance,但是實際上刪除節點很有可能會破壞樹的平衡性。刪除的程式碼如下:
Node* deleteMin(Node* x){
if(x->left == NULL) return x->right;
x->left = deleteMin(x->left);
x->N = 1+size(x->left) + size(x->right);
x->h = 1 + max(height(x->left),height(x->right));
return balance(x);
}
Node* deleteKey(Node* x,Key key){
if(x == NULL) return NULL;
if(key < x->key)
x->left = deleteKey(x->left,key); //遞迴刪除
else if(x->key < key){
x->right = deleteKey(x->right,key); //遞迴刪除
}
else{
//只有右節點
if(x->left == NULL){
return x->right;
}
//只有左節點
else if(x->right == NULL){
return x->left;
}
//有兩個孩子節點
else{
Node* t = x;
x = min(t->right); //找到右子樹中鍵值最小的節點
x->right = deleteMin(t->right); //把那個最小的節點刪除
x->left = t->left; //和上一個步驟一起完成了替換操作
}
}
x->N = 1+size(x->left) + size(x->right);
x->h = 1 + max(height(x->left),height(x->right));
return balance(x);
}
到此為止,關於AVL樹的兩個基本操作就完成了。剩下還有查詢,那個比較簡單,就不在文中贅述了,會在程式碼中給出。程式碼的具體實現利用到了模板,和文中的程式碼略有差異,不過影響不大,演算法思想是一模一樣的。經測試,程式碼在ubuntu16.04 g++ 5.4.0中可以編譯成功並執行。但是要提醒一下,由於程式碼和部落格是一天之內完成的,可能還有不夠嚴謹的地方,所以如果要拿去使用請小心測試後再使用。轉載請隨意,但煩勞註明出處。
程式碼和演算法思想大部分都是參考《演算法》(第4版),但是這本書並沒有給出AVL樹的實現,只實現了紅黑樹,不過配套的教學網站上給出了AVL樹的實現。下面是完整程式碼:
#include<iostream>
#include<string>
using namespace std;
template<typename Key, typename Value, typename Compare=less<Key> >
class Node{
public:
Key key;
Value val;
int h; //ÒԞÜڵãΪžùœÚµãµÄÊ÷µÄžß¶È
int N; //×ÓÊ÷ŽóС
Node* left;
Node* right;
Node(Key key, Value val, int h, int N){
this->key = key;
this->val = val;
this->h = h;
this->N = N;
}
};
template<typename Key, typename Value, typename Compare=less<Key> >
class AVLTree{
private:
Node<Key,Value,Compare>* root = NULL;
int max(int a, int b){
return ((a > b) ? a:b);
}
int height(Node<Key,Value,Compare>* x){
if(x == NULL) return -1;
return x->h;
}
int size(Node<Key,Value,Compare>* x){
if(x == NULL) return 0;
return x->N;
}
Node<Key,Value,Compare>* rotateLeft(Node<Key,Value,Compare>* x){
Node<Key,Value,Compare>* t = x->right;
x->right = t->left;
t->left = x;
x->N= 1 + size(x->left) + size(x->right);
x->h = 1+max(height(x->left),height(x->right));
t->h = 1+max(height(t->left),height(t->right));
return t;
}
Node<Key,Value,Compare>* rotateRight(Node<Key,Value,Compare>* x){
Node<Key,Value,Compare>* t = x->left;
x->left = t->right;
t->right = x;
t->N = x->N;
x->N= 1 + size(x->left) + size(x->right);
x->h = 1+max(height(x->left),height(x->right));
t->h = 1+max(height(t->left),height(t->right));
return t;
}
int balanceFactor(Node<Key,Value,Compare>* x){
return height(x->left) - height(x->right);
}
Node<Key,Value,Compare>* balance(Node<Key,Value,Compare>* x){
if(balanceFactor(x) < -1){
if(balanceFactor(x->right) > 0){
x->right = rotateRight(x->right);
}
x = rotateLeft(x);
}
else if(balanceFactor(x) > 1){
if(balanceFactor(x->left) < 0){
x->left = rotateLeft(x->left);
}
x = rotateRight(x);
}
return x;
}
Node<Key,Value,Compare>* put(Node<Key,Value,Compare>* x, Key key, Value val){
if(x == NULL) return new Node<Key,Value,Compare>(key,val,0,1);
if(key < x->key) x->left = put(x->left,key,val);
else if(x->key < key) x->right = put(x->right,key,val);
else {x->val = val; return x;}
x->N = 1+size(x->left) + size(x->right);
x->h = 1+max(height(x->left),height(x->right));
return balance(x);
}
Node<Key,Value,Compare>* deleteMin(Node<Key,Value,Compare>* x){
if(x->left == NULL) return x->right;
x->left = deleteMin(x->left);
x->N = 1+size(x->left) + size(x->right);
x->h = 1 + max(height(x->left),height(x->right));
return balance(x);
}
Node<Key,Value,Compare>* deleteMax(Node<Key,Value,Compare>* x){
if(x->right == NULL) return x->left;
x->right = deleteMax(x->right);
x->N = 1 + size(x->left) + size(x->right);
x->h = 1 + max(height(x->left),height(x->right));
return balance(x);
}
Node<Key,Value,Compare>* min(Node<Key,Value,Compare>* x){
if(x->left == NULL) return x;
return min(x->left);
}
Node<Key,Value,Compare>* max(Node<Key,Value,Compare>* x){
if(x->right == NULL) return x;
return max(x->right);
}
Value get(Node<Key,Value,Compare>* x,Key key){
if(x == NULL ) return NULL;
if(x->key < key) return get(x->right,key);
else if(key < x->key) return get(x->right,key);
else return x->val;
}
Node<Key,Value,Compare>* deleteKey(Node<Key,Value,Compare>* x,Key key){
if(x == NULL) return NULL;
if(key < x->key)
x->left = deleteKey(x->left,key);
else if(x->key < key){
x->right = deleteKey(x->right,key);
}
else{
//Ö»ÓÐÓҜڵã
if(x->left == NULL){
return x->right;
}
//Ö»ÓÐ×óœÚµã
else if(x->right == NULL){
return x->left;
}
//ÓÐÁœžöº¢×Ӝڵã
else{
Node<Key,Value,Compare>* t = x;
x = min(t->right);
x->right = deleteMin(t->right);
x->left = t->left;
}
}
x->N = 1+size(x->left) + size(x->right);
x->h = 1 + max(height(x->left),height(x->right));
return balance(x);
}
void printTree(Node<Key,Value,Compare>* x){
if(x == NULL) return;
printTree(x->left);
cout << x->key << " " << x->val << endl;
printTree(x->right);
}
public:
AVLTree(){
}
//žß¶È
int height(){
return height(root);
}
//ŽóС
int size(){
return size(root);
}
//²åÈ뺯Êý
void put(Key key, Value val){
root = put(root,key,val);
}
//getº¯Êý
Value get(Key key){
return get(root,key);
}
bool contains(Key key){
return get(key) != NULL;
}
bool isEmpty(){
return root==NULL;
}
//minº¯Êý
Key min(){
return min(root)->key;
}
Key max(){
return max(root)->key;
}
//deleteMinº¯Êý
void deleteMin(){
root = deleteMin(root);
}
void deleteMax(){
root = deleteMax(root);
}
void deleteKey(Key key){
root = deleteKey(root,key);
}
void printTree(){
printTree(root);
}
};
int main(){
AVLTree<string,int> st ;
st.put("S",1);
st.put("T",2);
st.put("A",3);
st.printTree();
st.deleteKey("S");
st.printTree();
}