【原文:https://cloud.tencent.com/developer/article/1155143】
AVL樹簡介
AVL樹的名字來源於它的發明作者G.M. Adelson-Velsky 和 E.M. Landis。AVL樹是最先發明的自平衡二叉查找樹(Self-Balancing Binary Search Tree,簡稱平衡二叉樹)。
一棵AVL樹有如下必要條件:
- 條件一:它必須是二叉查找樹。
- 條件二:每個節點的左子樹和右子樹的高度差至多為1。
圖一中左邊二叉樹的節點45的左孩子46比45大,不滿足二叉搜索樹的條件,因此它也不是一棵平衡二叉樹。 右邊二叉樹滿足二叉搜索樹的條件,同時它滿足條件二,因此它是一棵平衡二叉樹。
左邊二叉樹的節點45左子樹高度2,右子樹高度0,左右子樹高度差為2-0=2,不滿足條件二; 右邊二叉樹的節點均滿足左右子樹高度差至多為1,同時它滿足二叉搜索樹的要求,因此它是一棵平衡二叉樹。
AVL樹的查找、插入、刪除操作在平均和最壞的情況下都是O(logn),這得益於它時刻維護着二叉樹的平衡。如果我們需要查找的集合本身沒有順序,在頻繁查找的同時也經常的插入和刪除,AVL樹是不錯的選擇。不平衡的二叉查找樹在查找時的效率是很低的。
AVL樹相關概念
- 平衡因子:將二叉樹上節點的左子樹高度減去右子樹高度的值稱為該節點的平衡因子BF(Balance Factor)。
在圖二右邊的AVL樹上:
節點50的左子樹高度為3,右子樹高度為2,BF= 3-2 = 1;
節點45的左子樹高度為2,右子樹高度為1,BF= 2-1 = 1;
節點46的左子樹高度為0,右子樹高度為0,BF= 0-0 = 0;
節點65的左子樹高度為0,右子樹高度為1,BF= 0-1 = -1;
對於平衡二叉樹,BF的取值范圍為[-1,1]。如果發現某個節點的BF值不在此范圍,則需要對樹進行調整。
- 最小不平衡子樹:距離插入節點最近的,且平衡因子的絕對值大於1的節點為根的子樹。
圖三中,左邊二叉樹的節點45的BF = 1,插入節點43后,節點45的BF = 2。節點45是距離插入點43最近的BF不在[-1,1]范圍內的節點,因此以節點45為根的子樹為最小不平衡子樹。
AVL樹的實現詳解
節點結構
//結點類 template<typename T> struct AVLNode { T data; //數據成員 int height; //樹的高度 AVLNode<T>* leftChild; AVLNode<T>* rightChild; AVLNode() :leftChild(NULL), rightChild(NULL), height(0){} AVLNode(T d,AVLNode<T> *l=NULL,AVLNode<T>* r=NULL):data(d),leftChild(l),rightChild(r),height(0){} };
另外一些AVL實現的節點設計方案中,會把BF作為結點的一個屬性存儲起來,而在這里我們存儲的是節點的高度,通過節點的高度我們也可以間接計算出節點的BF。例如節點A的左孩子的height = 2,右孩子的height = 1,那么節點A的平衡因子為2 - 1 = 1.
AVL樹的高度
如前所說,我們的節點結構中並不存儲結點的BF,取而代之的是節點的高度。一個節點的BF可由其左右子樹的高度計算出來。我們提供返回一個節點高度的操作
//求一棵樹的高度 int Height(AVLNode<T>* cur) { if (cur == NULL) return 0; int i = Height(cur->leftChild); int j = Height(cur->rightChild); return i < j ? j + 1 : i + 1; }
AVL樹失衡調整
節點的插入或刪除都有可能導致AVL樹失去平衡,因此,失衡調整是插入與刪除操作的基礎。 AVL樹的失衡調整可以分為四種情況,我們逐一分析。 假設我們要為數組a[]={4,5,6,3,2,8,7,0,1}構建一棵AVL樹。
情況一:左單旋轉
首先插入{4,5,6},在插入元素6后出現不平衡的情況:
當我們在右子樹插入右孩子導致AVL失衡時,我們需要進行單左旋調整。旋轉圍繞最小失衡子樹的根節點進行。 在刪除新節點時也有可能會出現需要單左旋的情況。 左旋代碼如下:
AVLNode<T>* LeftRotation(AVLNode<T>* cur) { AVLNode<T>* prchid = cur->rightChild; cur->rightChild = prchid->leftChild; prchid->leftChild = cur; //改變了指向后,還要更新結點對應的高度 cur->height = max(Height(cur->leftChild),Height(cur->rightChild))+1; prchid->height = max(Height(prchid->leftChild), Height(prchid->rightChild)) + 1; return prchid; }
結合例子進行分析:
- 參數cur為最小失衡子樹的根節點,在圖四中為節點4
- 若節點5有左子樹,則該左子樹成為節點4的右子樹
- 節點4成為節點5的左子樹
- 最后更新節點的高度值
情況二:右單旋轉
我們繼續插入元素{3,2},此時二叉樹為:
插入3、2后出現了不平衡的情況。此時的插入情況是“在左子樹上插入左孩子導致AVL樹失衡”,我們需要進行單右旋調整。 單右旋代碼為:
AVLNode<T>* RightRotation(AVLNode<T>* cur) { AVLNode<T>* plchild = cur->leftChild; cur->leftChild = plchild->rightChild; plchild->rightChild = cur; cur->height = max(Height(cur->leftChild), Height(cur->rightChild)) + 1; plchild->height = max(Height(plchild->leftChild), Height(plchild->rightChild)) + 1; return plchild; }
結合例子進行分析:
- 參數cur為最小失衡子樹的根節點,在圖四中為節點4
- 若節點3有右子樹,則該右子樹成為節點4的左子樹
- 節點4成為節點3的左子樹
- 調整節點的高度值
情況三:先右旋后左旋
需要進行兩次旋轉的原因是第一次旋轉后,AVL樹仍舊處於不平衡的狀態,第二次旋轉再次進行調整。 我們繼續插入元素{8,7}
這種情況,總結起來就是“在右子樹上插入左孩子導致AVL樹失衡",此時我們需要進行先右旋后左旋的調整。 調整的代碼為:
AVLNode<T>* RightLeftRotation(AVLNode<T> *cur) { cur->rightChild = RightRotation(cur->rightChild); return LeftRotation(cur); };
結合例子進行分析:
- 首先對最小不平衡子樹的根節點(也就是節點6)的右孩子(也就是8)進行右旋操作
- 再對節點6進行一次左旋操作
情況四:先左旋后右旋
根據對稱性原理,當我們“在左子樹上插入右孩子導致AVL樹失衡",此時我們需要進行先左旋后右旋的調整。如果你不理解接着看圖。 我們接着插入節點{0,1}
調整的代碼:
AVLNode<T>* LeftRightRotation(AVLNode<T> *cur) { cur->leftChild = LeftRotation(cur->leftChild); return RightRotation(cur); };
結合例子進行分析:
- 首先對最小不平衡子樹的根節點(也就是節點2)的左孩子(也就是0)進行左旋操作
- 再對節點2進行一次右旋操作
總結
單左旋
在右子樹插入右孩子節點,使得平衡因子絕對值由1增至2
單右旋
在左子樹插入左孩子節點,使得平衡因子絕對值由1增至2
先左旋后右旋
在左子樹插入右孩子節點,使得平衡因子絕對值由1增至2
先右旋后左旋
在右子樹插入左孩子節點,使得平衡因子絕對值由1增至2
插入新節點
其實上面已經展示了一個完整的插入過程,結合例子很好理解下面這段代碼。
AVLNode<T>* Insert(AVLNode<T>* &cur,T key) { if(cur==NULL) //尋找插入位置 { cur = new AVLNode<T>(key); } else if(key>cur->data) //插入值比當前結點值大,插入到當前結點的右子樹上 { cur->rightChild = Insert(cur->rightChild, key); if(Height(cur->rightChild)-Height(cur->leftChild)==2) //插入后出現失衡 { if (key > cur->rightChild->data) //情況一:插入右子樹的右節點,進行左旋 cur = LeftRotation(cur); else if (key < cur->rightChild->data) //情況三:插入右子樹的左節點,進行先右再左旋轉 cur = RightLeftRotation(cur); } } else if(key<cur->data) //插入值比當前節點值小,插入到當前結點的左子樹上 { cur->leftChild = Insert(cur->leftChild, key); if(Height(cur->leftChild)-Height(cur->rightChild)==2)//如果插入導致失衡 { if (key < cur->leftChild->leftChild->data) cur = RightRotation(cur); //情況二:插入到左子樹的左孩子節點上,進行右旋 else if (key > cur->leftChild->data) cur = LeftRightRotation(cur); } } cur->height = max(Height(cur->leftChild), Height(cur->rightChild)) + 1; return cur; }
刪除節點
刪除節點也可能導致AVL樹的失衡,實際上刪除節點和插入節點是一種互逆的操作:
- 刪除右子樹的節點導致AVL樹失衡時,相當於在左子樹插入節點導致AVL樹失衡,即情況情況二或情況四。
- 刪除左子樹的節點導致AVL樹失衡時,相當於在右子樹插入節點導致AVL樹失衡,即情況情況一或情況三。
另外,AVL樹也是一棵二叉排序樹,因此在刪除節點時也要維護二叉排序樹的性質。
刪除的代碼實現:
AVLNode<T>* Remove(AVLNode<T>* &cur,T key) { if(cur!=NULL) //找到刪除的節點 { if(key==cur->data) //找到刪除的節點 { //因AVL也是二叉排序樹,刪除節點要維護其二叉排序樹的條件 if(cur->leftChild!=NULL&&cur->rightChild!=NULL) //有兩個孩子 { //左子樹比右子樹高 if(Height(cur->leftChild)>Height(cur->rightChild)) { AVLNode<T>* pmax = maximum(cur->leftChild); cur->data = pmax->data; cur->leftChild = Remove(cur->leftChild, pmax->data); } else //左子樹比右子樹低 { AVLNode<T>* pmin = minimum(cur->rightChild); cur->data = pmin->data; cur->rightChild = Remove(cur->rightChild, pmin->data); } } else //只有一個孩子,直接讓這個孩子結點取代當前結點 { AVLNode<T>* pTemp = cur; if (cur->leftChild != NULL) cur = cur->leftChild; else if (cur->rightChild != NULL) cur = cur->rightChild; delete pTemp; return NULL; } } else if (key>cur->data) //要刪除的節點比當前節點大,則在右子樹進行刪除 { cur->rightChild = Remove(cur->rightChild, key); if (Height(cur->leftChild) - Height(cur->rightChild) == 2) //刪除右子樹節點導致不平衡:相當於情況二或情況四 { if (Height(cur->leftChild->rightChild) > Height(cur->leftChild->leftChild)) cur = LeftRightRotation(cur); //相當於情況四 else cur = RightRotation(cur); //相當於情況二 } } else if(key<cur->data) //要刪除的節點比當前節點小,則在左子樹進行刪除 { cur->leftChild = Remove(cur->leftChild, key); //右子樹比左子數高 if(Height(cur->rightChild)-Height(cur->leftChild)==2) //刪除左子樹節點導致不平衡:相當於情況三或情況一 { //右子樹左邊高導致不平衡,要先右旋,在左旋調整 if (Height(cur->rightChild->leftChild) > Height(cur->rightChild->rightChild)) cur = RightLeftRotation(cur); else cur = LeftRotation(cur); } } return cur; } return NULL; }
- 刪除節點時,如果節點同時擁有左子樹和右子樹,則在高度教低的子樹上選擇最大(或最小)元素進行替換,這樣能保證替換后不會再出現失衡的現象。
至此,AVL樹較為復雜的部分都已經分析完畢。剩下的其他操作是普通的二叉排序樹共通的操作。為了完整性,我們簡單說一下代碼。
查找元素
二叉樹是一種遞歸的定義,因此,二叉樹的許多操作都可以通過遞歸簡單地實現,例如遍歷二叉樹、查找指定元素、銷毀二叉樹等。 基於二叉排序樹的特殊性質, 元素查找操作也能夠使用非遞歸算法簡單地實現,我們提供遞歸與非遞歸兩種版本的元素查找算法。
AVLNode<T>* SearchRecurse(AVLNode<T>* cur,T key) { if(cur!=NULL) { if (key == cur->data) return cur; if (key > cur->data) return SearchRecurse(cur->rightChild, key); else return SearchRecurse(cur->leftChild, key); } return NULL; } AVLNode<T>* SearchIterator(AVLNode<T>* cur,T key) { while (cur!=NULL) { if (cur->data == key) return cur; else if (key > cur->data) cur = cur->rightChild; else cur = cur->leftChild; } return NULL; }
AVL樹的銷毀
采用后序遍歷AVL樹來銷毀二叉樹。即先銷毀根節點的左子樹,然后銷毀根節點的右子樹,最后才銷毀根節點。
void Destroy(AVLNode<T>* &cur) { if(cur!=NULL) { Destroy(cur->leftChild); Destroy(cur->rightChild); delete cur; cur = NULL; } }
求最大最小值
二叉排序樹的最小值位於最左節點,最大值位於其最右節點。
//返回以cur為根的最大結點的地址 AVLNode<T>* maximum(AVLNode<T>* cur) { if(cur!=NULL) { while (cur->rightChild!=NULL) { cur = cur->rightChild; } return cur; } return NULL; } //返回以cur為根的最小結點的地址 AVLNode<T>* minimum(AVLNode<T>* cur) { if(cur!=NULL) { while (cur->leftChild=NULL) { cur = cur->leftChild; } return cur; } return NULL; }
完整代碼
//結點類 template<typename T> struct AVLNode { T data; //數據成員 int height; //樹的高度 AVLNode<T>* leftChild; AVLNode<T>* rightChild; AVLNode() :leftChild(NULL), rightChild(NULL), height(0){} AVLNode(T d,AVLNode<T> *l=NULL,AVLNode<T>* r=NULL):data(d),leftChild(l),rightChild(r),height(0){} }; //AVL類型 template<typename T> class AVLTree { public: //構造空樹 AVLTree():root(NULL){} //析構函數 ~AVLTree() { Destroy(root); } //遞歸方式查找 AVLNode<T>* SearchRecurse(T key) { return SearchRecurse(root, key); } //非遞歸(迭代)方式查找 AVLNode<T>* SearchIterator(T key) { return SearchIterator(root,key); } //插入 bool Insert(T key) { return Insert(root, key); } //刪除 bool Remove(T key) { return Remove(root, key); } //求高度 int Height() { return Height(root); } //返回兩個中較大的數 int max(int a, int b) { return a > b ? a : b; } //中序遍歷 void InOrder() { InOrder(root); } //以廣義表的形式輸出二叉樹(前序遍歷的應用) void PrintBinTree() { PrintBinTree(root); } private: /*左旋轉操作*/ /*cur為最小失衡子樹的根節點*/ /*返回旋轉后的根節點*/ /* * 9 * / \ * 6 12 * / \ * 3 15 * / \ * 13 19 */ AVLNode<T>* LeftRotation(AVLNode<T>* cur) { AVLNode<T>* prchid = cur->rightChild; cur->rightChild = prchid->leftChild; prchid->leftChild = cur; //改變了指向后,還要更新結點對應的高度 cur->height = max(Height(cur->leftChild),Height(cur->rightChild))+1; prchid->height = max(Height(prchid->leftChild), Height(prchid->rightChild)) + 1; return prchid; } /*右旋轉操作*/ /*cur為最小失衡子樹的根節點*/ /*返回旋轉后的根節點*/ /* * 9 * / \ * 6 12 * / \ * 3 15 * / \ * 1 4 */ AVLNode<T>* RightRotation(AVLNode<T>* cur) { AVLNode<T>* plchild = cur->leftChild; cur->leftChild = plchild->rightChild; plchild->rightChild = cur; cur->height = max(Height(cur->leftChild), Height(cur->rightChild)) + 1; plchild->height = max(Height(plchild->leftChild), Height(plchild->rightChild)) + 1; return plchild; } /*先左后右做旋轉*/ /*參數cur為最小失衡子樹的根節點*/ /*返回旋轉后的根節點*/ AVLNode<T>* LeftRightRotation(AVLNode<T> *cur) { cur->leftChild = LeftRotation(cur->leftChild); return RightRotation(cur); }; /*先右旋再左旋*/ /*參數cur為最小失衡子樹的根節點*/ /*返回旋轉后的根節點*/ AVLNode<T>* RightLeftRotation(AVLNode<T> *cur) { cur->rightChild = RightRotation(cur->rightChild); return LeftRotation(cur); }; /* * 8 * / \ * 3 10 */ //插入結點 AVLNode<T>* Insert(AVLNode<T>* &cur,T key) { if(cur==NULL) //尋找插入位置 { cur = new AVLNode<T>(key); } else if(key>cur->data) //插入值比當前結點值大,插入到當前結點的右子樹上 { cur->rightChild = Insert(cur->rightChild, key); if(Height(cur->rightChild)-Height(cur->leftChild)==2) //插入后出現失衡 { if (key > cur->rightChild->data) //情況一:插入右子樹的右節點,進行左旋 cur = LeftRotation(cur); else if (key < cur->rightChild->data) //情況三:插入右子樹的左節點,進行先右再左旋轉 cur = RightLeftRotation(cur); } } else if(key<cur->data) //插入值比當前節點值小,插入到當前結點的左子樹上 { cur->leftChild = Insert(cur->leftChild, key); if(Height(cur->leftChild)-Height(cur->rightChild)==2)//如果插入導致失衡 { if (key < cur->leftChild->leftChild->data) cur = RightRotation(cur); //情況二:插入到左子樹的左孩子節點上,進行右旋 else if (key > cur->leftChild->data) cur = LeftRightRotation(cur); } } cur->height = max(Height(cur->leftChild), Height(cur->rightChild)) + 1; return cur; } /* * 8 * / \ * 3 10 * /\ /\ * 1 6 9 11 */ //刪除結點 AVLNode<T>* Remove(AVLNode<T>* &cur,T key) { if(cur!=NULL) //找到刪除的節點 { if(key==cur->data) //找到刪除的節點 { //因AVL也是二叉排序樹,刪除節點要維護其二叉排序樹的條件 if(cur->leftChild!=NULL&&cur->rightChild!=NULL) //有兩個孩子 { //左子樹比右子樹高 if(Height(cur->leftChild)>Height(cur->rightChild)) { AVLNode<T>* pmax = maximum(cur->leftChild); cur->data = pmax->data; cur->leftChild = Remove(cur->leftChild, pmax->data); } else //左子樹比右子樹低 { AVLNode<T>* pmin = minimum(cur->rightChild); cur->data = pmin->data; cur->rightChild = Remove(cur->rightChild, pmin->data); } } else //只有一個孩子,直接讓這個孩子結點取代當前結點 { AVLNode<T>* pTemp = cur; if (cur->leftChild != NULL) cur = cur->leftChild; else if (cur->rightChild != NULL) cur = cur->rightChild; delete pTemp; return NULL; } } else if (key>cur->data) //要刪除的節點比當前節點大,則在右子樹進行刪除 { cur->rightChild = Remove(cur->rightChild, key); if (Height(cur->leftChild) - Height(cur->rightChild) == 2) //刪除右子樹節點導致不平衡:相當於情況二或情況四 { if (Height(cur->leftChild->rightChild) > Height(cur->leftChild->leftChild)) cur = LeftRightRotation(cur); //相當於情況四 else cur = RightRotation(cur); //相當於情況二 } } else if(key<cur->data) //要刪除的節點比當前節點小,則在左子樹進行刪除 { cur->leftChild = Remove(cur->leftChild, key); //右子樹比左子數高 if(Height(cur->rightChild)-Height(cur->leftChild)==2) //刪除左子樹節點導致不平衡:相當於情況三或情況一 { //右子樹左邊高導致不平衡,要先右旋,在左旋調整 if (Height(cur->rightChild->leftChild) > Height(cur->rightChild->rightChild)) cur = RightLeftRotation(cur); else cur = LeftRotation(cur); } } return cur; } return NULL; } //求一棵樹的高度 int Height(AVLNode<T>* cur) { if (cur == NULL) return 0; int i = Height(cur->leftChild); int j = Height(cur->rightChild); return i < j ? j + 1 : i + 1; } //返回以cur為根的最大結點的地址 AVLNode<T>* maximum(AVLNode<T>* cur) { if(cur!=NULL) { while (cur->rightChild!=NULL) { cur = cur->rightChild; } return cur; } return NULL; } //返回以cur為根的最小結點的地址 AVLNode<T>* minimum(AVLNode<T>* cur) { if(cur!=NULL) { while (cur->leftChild=NULL) { cur = cur->leftChild; } return cur; } return NULL; } //中序遍歷 void InOrder(AVLNode<T>* cur) { if(cur!=NULL) { InOrder(cur->leftChild); cout << cur->data << " "; InOrder(cur->rightChild); } } //以廣義表形式輸出二叉樹 void PrintBinTree(AVLNode<T>* BT) { if (BT != NULL) //樹為空時結束遞歸 { cout << BT->data; if (BT->leftChild != NULL || BT->rightChild != NULL) { cout << '('; if (BT->leftChild != NULL) { PrintBinTree(BT->leftChild); } cout << ','; if (BT->rightChild != NULL) { PrintBinTree(BT->rightChild); } cout << ')'; } } } //銷毀 void Destroy(AVLNode<T>* &cur) { if(cur!=NULL) { Destroy(cur->leftChild); Destroy(cur->rightChild); delete cur; cur = NULL; } } //遞歸查找 AVLNode<T>* SearchRecurse(AVLNode<T>* cur,T key) { if(cur!=NULL) { if (key == cur->data) return cur; if (key > cur->data) return SearchRecurse(cur->rightChild, key); else return SearchRecurse(cur->leftChild, key); } return NULL; } //非遞歸查找 AVLNode<T>* SearchIterator(AVLNode<T>* cur,T key) { while (cur!=NULL) { if (cur->data == key) return cur; else if (key > cur->data) cur = cur->rightChild; else cur = cur->leftChild; } return NULL; } private: AVLNode<T>* root; };
測試代碼
int main() { AVLTree<int> t; for (int i = 0; i < 10; i++) t.Insert(i); cout <<"樹高:"<< t.Height()<<endl; t.PrintBinTree(); t.Remove(5); cout << endl; t.PrintBinTree(); cout << endl; cout << t.SearchRecurse(7)->data << endl; cout << t.SearchIterator(6)->data << endl; return 0; }