
這篇文章用來復習AVL的平衡操作,分別會介紹其旋轉操作的遞歸與非遞歸實現,但是最終帶有插入示例的版本會以遞歸呈現.
下面這張圖繪制了需要旋轉操作的8種情況.(我要給做這張圖的兄弟一個贊)后面會給出這八種情況對應平衡實現.
[1]

情況1-2:
這種需要旋轉的結構一般稱之為LL型,需要右旋 (順時針旋轉).
我用一個圖來抽象一下這兩個情況,畫的不好,我盡量表達吧.

此時需要對A進行平衡操作,方法為:
- 將A的左子樹換為B的右子樹.
- B的右子樹換為A.
- 非遞歸實現的代碼為:
1 void rotate_right(AVLTree &A){ 2 3 AVLTree leftChild = A->left; 4 5 A->left = leftChild->right; 6 7 leftChild->right = A; 8 9 // 別忘了讓父節點建立平衡后的連接 10 11 A = leftChild; 12 13 }
非遞歸的操作在旋轉前會充分考慮所有的旋轉情況,目的是提早調整A下面各節點的高度.
之后再進行旋轉操作,這一點與遞歸的不同,可見遞歸是平衡完后再進行的高度調整.
- 遞歸實現代碼為:
1 Position CAVLTree::singleRotateWithLeft(Position _K){ 2 Position K0; 3 K0 = _K->left; 4 _K->left = K0->right; 5 K0->right = _K; 6 7 _K->Height = max(getHeight(_K->left),getHeight(_K->right)) + 1; 8 K0->Height = max(getHeight(K0->left),getHeight(K0->right)) + 1; 9 10 // 返回新的節點以替換 11 return K0; 12 }
情況3-4:
這種需要旋轉的結構一般稱之為RR型,需要左旋(逆時針旋轉).
需要對A進行平衡操作,方法為:
- 將A的右子樹換為B的左子樹;
- B的左子樹換為A

- 非遞歸的實現為:
void rotate_left(AVLTree &A){ AVLTree rightChild = A->right; A->right = rightChild ->left; rightChild->left = A; A = rightChild; }
- 遞歸實現為:
Position CAVLTree::singleRotateWithRight(Position _K){ Position K0; K0 = _K->right; _K->right = K0->left; K0->left = _K; _K->Height = max(getHeight(_K->left),getHeight(_K->right)) + 1; K0->Height = max(getHeight(K0->left),getHeight(K0->right)) + 1; return K0; }
情況5-6:
這種需要旋轉的結構一般稱之為LR型,需要雙旋轉,即兩次單旋.分別為左旋和右旋.
需要對A進行平衡操作,方法為:
- 對B(A->left)做左旋
- 對A做右旋

這個遞歸與非遞歸的方式都是一樣的.
- 非遞歸:
rotate_left(A->left);
rotate_right(A);
- 遞歸:
Position CAVLTree::doubleRotateWithLeft(Position _K){ _K->left = singleRotateWithRight(_K->left); return singleRotateWithLeft(_K); }
但是有沒有一次性到位的方法呢?有的
我把非遞歸的兩個函數展開:
發現最后一步都是確定與父節點的關系,並不是旋轉中的具體過程,於是可以簡化為這樣:
AVLTree leftChild = A->left; AVLTree leftRightChild = leftChild->left; // 左旋 leftChild->right = leftRightChild->left; leftRightChild->left = leftChild; // 右旋 A->left = leftRightChild->right; leftChild->right = A;
情況7-8:
這種需要旋轉的結構一般稱之為RL型,需要雙旋轉,即兩次單旋.分別為右旋和左旋.
需要對A進行平衡操作,方法為:
- 對B進行右旋
- 對A進行左旋

同樣,遞歸與非遞歸版本是一樣的.
- 非遞歸:
rotate_right(A->left);
rotate_left(A);
- 遞歸:
Position CAVLTree::doubleRotateWithRight(Position _K){ _K->right = singleRotateWithLeft(_K->right); return singleRotateWithRight(_K); }
同樣,也有一次性到位的方法:
AVLTree rightChild = A->right; AVLTree rightLeftChild = rightChild->left; // 右旋 rightChild->left = rightLeftChild->right; rightLeftChild->right = rightChild; // 左旋 A->right = rightLeftChild->left; rightLeftChild->left = A;
下面是實現部分:
0.結構聲明[2]:
struct AvlNode; typedef AvlNode * AvlTree; typedef AvlNode * Position; typedef int ELEMENT; struct AvlNode { AvlNode():data(),left(nullptr),right(nullptr),Height(){} ELEMENT data; AvlTree left; AvlTree right; int Height; };
1.類中提供的API
class CAVLTree { public: CAVLTree(void); ~CAVLTree(void); size_t _insert_(ELEMENT &_data); int getTreeHeight(); void showThisTree(); private: size_t size; AvlTree AvlTreeRoot; private: Position insert_specific(ELEMENT &_data,AvlTree &_T); void showThisTree_specific(AvlTree _T); int getTreeHeight_specific(AvlTree _T); int max(int _a,int _b); int getHeight(Position _K); // 對於左左的分支,采用右旋 Position singleRotateWithLeft(Position _K); //對於右右的分支,采用左旋 Position singleRotateWithRight(Position _K); // 對於左右的分支,采用先左旋后右旋 Position doubleRotateWithLeft(Position _K); // 對於右左的分支,采用先右旋后左旋 Position doubleRotateWithRight(Position _K); };
2.獲取高度:
因為在max()函數獲取結束后需要+1,所以這里的目的是將葉節點的Height想辦法為0.
int CAVLTree::getHeight(Position _K){ return (_K == nullptr )?-1:_K->Height; }
3.插入操作:
- 遞歸
通過回溯的方式找到插入的位置,先平衡后調整高度;
哈哈,有一個很有趣的細節為什么同時判斷高度差一個是
if(getHeight(_T->left) - getHeight(_T->right) == 2)
而另一個是
if (getHeight(_T->right) - getHeight(_T->left) == 2)
因為這里已經知道了插入發生在哪邊了,所以肯定是插入的那邊會有破壞平衡的可能,不會造成尷尬的(小-大)的局面.
Position CAVLTree::insert_specific(ELEMENT &_data,AvlTree &_T){ if (!_T) { _T = new AvlNode; _T->data = _data; _T->Height = 0; size++; } else if(_data < _T->data) { _T->left = insert_specific(_data,_T->left); if(getHeight(_T->left) - getHeight(_T->right) == 2) { // 根據新插入的節點所在位置來判斷使用什么旋轉 if(_data < _T->left->data) { // 需要右旋 _T = singleRotateWithLeft(_T); } else { // 需要先左旋后右旋 _T = doubleRotateWithLeft(_T); } } } else if (_data > _T->data) { _T->right = insert_specific(_data,_T->right); if (getHeight(_T->right) - getHeight(_T->left) == 2) { if (_data > _T->right->data) { // 需要左旋 _T = singleRotateWithRight(_T); } else { // 需要先右旋再左旋 _T = doubleRotateWithRight(_T); } } } _T->Height = max(getHeight(_T->left) , getHeight(_T->right)) + 1 ; return _T; }
- 非遞歸[3]:
可以發現,非遞歸的實現是先調整高度再平衡,但是要提前考慮所有情況.
考慮左子樹的情況:
void leftBalance(AVLNode* &t) { AVLNode* lc = NULL; AVLNode* rd = NULL; lc = t->lchild; switch(lc->bf) { case LH: //順時針旋轉(即右旋) t->bf = EH; lc->bf = EH; R_Rotate(t); break; case EH: //刪除節點時會發生,插入不會發生 t->bf = LH; lc->bf = RH; R_Rotate(t); break; case RH: //先左旋后右旋 rd = lc->rchild; switch(rd->bf) { case LH: t->bf = RH; lc->bf = EH; break; case EH: t->bf = EH; lc->bf = EH; break; case RH: t->bf = EH; lc->bf = LH; break; } rd->bf = EH; L_Rotate(t->lchild);//不能寫L_Rotate(lc);采用的是引用參數 R_Rotate(t); break; } }
考慮右子樹的情況:
void rightBalance(AVLNode* &t) { AVLNode* rc = NULL; AVLNode *ld = NULL; rc = t->rchild; switch(rc->bf) { case RH: //逆時針旋轉(即左旋) t->bf = EH; rc->bf = EH; L_Rotate(t); break; case EH: //刪除節點時會發生,插入不會發生 t->bf = RH; rc->bf = LH; L_Rotate(t); break; case LH: //先右旋后左旋 ld = rc->lchild; switch(ld->bf) { case LH: t->bf = EH; rc->bf = RH; break; case EH: t->bf = EH; rc->bf = EH; break; case RH: t->bf = LH; rc->bf = EH; break; } ld->bf = EH; R_Rotate(t->rchild);//不能寫R_Rotate(rc);采用的是引用參數 L_Rotate(t); break; } }
總結:
遞歸真是神奇啊,對子樹的處理遞歸的很漂亮,代碼量是一方面,代碼邏輯的清晰性也是非遞歸程序鮮有的.
用這個來學習遞歸算法真是好工具,希望對於我后面復習圖論有幫助.
這篇文章中所述的非遞歸程序我並沒有實現,肯定有疏忽的地方,歡迎大家指正.
完整示例中還有一個showThisTree(),它可以打印出漂亮的平衡二叉樹.

相關代碼請見我的github
[1] AVL樹的旋轉操作 圖解 最詳細
[2] left 等價 leftChild,同理,right 也等價 rightChild.
[4] 參考教材 數據結構與算法分析:C語言描述(原書第2版)[美] MarkAllenWeiss 著
