目錄
一、AVL樹的概念
二、AVL樹的實現
2.1節點定義
?2.2節點插入
三、AVL樹的旋轉
3.1新節點插入較高左子樹的左側:右單旋
3.2新節點插入較高右子樹的右側:左單旋
3.3新節點插入較高左子樹的右側---左右:先左單旋再右單旋
3.4新節點插入較高右子樹的左側---右左:先右單旋再左單旋
四、AVL樹的性能
一、AVL樹的概念
二叉搜索樹雖可以縮短查找的效率,但如果數據有序或接近有序二叉搜索樹將退化為單支樹,查
找元素相當于在順序表中搜索元素,效率低下。因此,兩位俄羅斯的數學家G.M. A delson- V elskii
和E.M. L andis在1962年 發明了一種解決上述問題的方法:當向二叉搜索樹中插入新結點后,如果能保證每個結點的左右 子樹高度之差的絕對值不超過1(需要對樹中的結點進行調整),即可降低樹的高度,從而減少平均 搜索長度。
一棵AVL樹或者是空樹,或者是具有以下性質的二叉搜索樹:
它的左右子樹都是AVL樹 左右子樹高度之差(簡稱平衡因子)的絕對值不超過1(-1/0/1)

如果一棵二叉搜索樹是高度平衡的,它就是AVL樹。如果它有n個結點,其高度可保持在
$O(log_2 n)$,搜索時間復雜度logn。
二、AVL樹的實現
2.1節點定義
template <class K,class V>
class AVLtreeNode
{AVLtreeNode<K, V>* _left;AVLtreeNode<K, V>* _right;AVLtreeNode<K, V>* _parent;pair<K, V> _kv;int bf;AVLtreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), bf(0){}
};
?2.2節點插入
AVL樹就是在二叉搜索樹的基礎上引入了平衡因子,因此AVL樹也可以看成是二叉搜索樹。那么
AVL樹的插入過程可以分為兩步:
1. 按照二叉搜索樹的方式插入新節點
2. 調整節點的平衡因子
新節點插入后,AVL樹的平衡性可能會遭到破壞,此時就需要更新平衡因子,并檢測是否破壞了AVL樹的平衡性。
pCur插入后,pParent的平衡因子一定需要調整,在插入之前,pParent的平衡因子分為三種情況:-1,0, 1, 分以下兩種情況:
?1. 如果pCur插入到pParent的左側,只需給pParent的平衡因子-1即可
?2. 如果pCur插入到pParent的右側,只需給pParent的平衡因子+1即可
此時:pParent的平衡因子可能有三種情況:0,正負1, 正負2
?1. 如果pParent的平衡因子為0,說明插入之前pParent的平衡因子為正負1,插入后被調整成0,此時滿足 AVL樹的性質,插入成功
?2. 如果pParent的平衡因子為正負1,說明插入前pParent的平衡因子一定為0,插入后被更新成正負1,此時以pParent為根的樹的高度增加,需要繼續向上更新
?3. 如果pParent的平衡因子為正負2,則pParent的平衡因子違反平衡樹的性質,需要對其進行旋轉處理
bool insert(const pair<K, V>& kv){if (_root == nullptr){_root = new(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;} }cur = new Node(kv);if (parent->_kv.first>cur->_kv.first){parent->_left = cur;}else{parent->_right = cur;} cur->_parent = parent;//調整avl樹的結構,處理parent的平衡因子while (parent){if (parent->left == cur){parent->bf--;}else if (parent->right == cur){parent->bf++;}//不斷向上更改avl樹中的bf平衡因子if (parent->_bf == 1 || parent->_bf == -1){//更新去上面的父節點的bfparent = parent->_parent;cur = cur->parent;}else if (parent->_bf == 2 || parent->_bf == -2){//旋轉處理//單旋if (parent->_bf == 2 && cur->_bf == 1)//右邊高 {RotateL(parent);//左單旋}else if (parent->_bf == -2 && cur->_bf == -1)//左邊高{RotateR(parent);//右單旋}//雙旋處理else if (parent->_bf==-2&&cur->_bf==1)//左邊高的右邊高,先左旋再右旋{RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent)//右邊高的左邊高,先右旋再左旋}else{assert(false);}break;}else{assert(false);}}return true;}
三、AVL樹的旋轉
如果在一棵原本是平衡的AVL樹中插入一個新節點,可能造成不平衡,此時必須調整樹的結構,使之平衡化。根據節點插入位置的不同,AVL樹的旋轉分為四種:
3.1新節點插入較高左子樹的左側:右單旋
上圖在插入前,AVL樹是平衡的,新節點插入到30的左子樹(注意:此處不是左孩子)中,30左子樹增加了一層,導致以60為根的二叉樹不平衡,要讓60平衡,只能將60左子樹的高度減少一層,右子樹增加一層,即將左子樹往上提,這樣60轉下來,因為60比30大,只能將其放在30的右子樹,而如果30有右子樹,右子樹根的值一定大于30,小于60,只能將其放在60的左子樹,旋轉完成后,更新節點的平衡因子即可。在旋轉過程中,有以下幾種情況需要考慮:
1. 30節點的右孩子可能存在,也可能不存在。
2. 60可能是根節點,也可能是子樹如果是根節點,旋轉完成后,要更新根節點如果是子樹,可能是某個節點的左子樹,也可能是右子樹。
void RotateR(Node* parent)//右單旋{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;parent->_left = subLR;if (subLR) subLR->_parent = parent;subL->_right = parent;parent->_parent = subL;if (pparent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left == subL;}else{pparent->_right == subL;}subL->_parent = pparent;}subL->_bf = parent->_bf = 0;return true;}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent); if (bf == 1)//左邊高的右邊高,新增節點在右{parent->_bf = 0;subLR->_bf = 0;subL->_bf = -1;}else if (bf == -1)//新增節點在左{parent->_bf = 1;subLR->_bf = 0;subL->_bf = 0 ;}else if (bf == 0)//本身就是新增節點直接導致出現左邊高的右邊高{parent->_bf = 0;subLR->_bf = 0;subL->_bf = 0;}else{assert(false);}}
3.2新節點插入較高右子樹的右側:左單旋
和右單旋的思路以及實現方式大相徑庭。只需略微改動。
void RotateL(Node* parent)//左單旋{Node* subR = parent->right;Node* subRL = subR->left;Node* pparent = parent->_parent;parent->right = subRL;if(subRL)subRL->_parent = parent;subR->left = parent;parent->_parent = subR;if (pparent == nullptr){_root = subR;_root->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}subR->_parent = pparent;}parent->_bf = subR->_bf = 0;}
3.3新節點插入較高左子樹的右側---左右:先左單旋再右單旋
將雙旋變成單旋后再旋轉,即:先對30進行左單旋,然后再對90進行右單旋,旋轉完成后再
考慮平衡因子的更新。
void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent); if (bf == 1)//左邊高的右邊高,新增節點在右{parent->_bf = 0;subLR->_bf = 0;subL->_bf = -1;}else if (bf == -1)//新增節點在左{parent->_bf = 1;subLR->_bf = 0;subL->_bf = 0 ;}else if (bf == 0)//本身就是新增節點直接導致出現左邊高的右邊高{parent->_bf = 0;subLR->_bf = 0;subL->_bf = 0;}else{assert(false);}}
3.4新節點插入較高右子樹的左側---右左:先右單旋再左單旋
參考右左雙旋
void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL =subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 1){parent->_bf = -1;subRL->_bf = 0;subR->_bf = 0;}else if (bf == -1){parent->_bf = 0;subRL->_bf = 0;subR->_bf = 1;}else if (bf == 0){parent->_bf = 0;subRL->_bf = 0;subR->_bf = 0;}else{assert(false);}}
總結:
假如以pParent為根的子樹不平衡,即pParent的平衡因子為2或者-2,分以下情況考慮
1. pParent的平衡因子為2,說明pParent的右子樹高,設pParent的右子樹的根為pSubR。
當pSubR的平衡因子為1時,執行左單旋。
當pSubR的平衡因子為-1時,執行右左雙旋。
2. pParent的平衡因子為-2,說明pParent的左子樹高,設pParent的左子樹的根為pSubL。
當pSubL的平衡因子為-1是,執行右單旋。
當pSubL的平衡因子為1時,執行左右雙旋。
旋轉完成后,原pParent為根的子樹個高度降低,已經平衡,不需要再向上更新。
四、AVL樹的性能
因為AVL樹也是二叉搜索樹,可按照二叉搜索樹的方式將節點刪除,然后再更新平衡因子,只不 錯與刪除不同的時,刪除節點后的平衡因子更新,最差情況下一直要調整到根節點的位置。
AVL樹是一棵絕對平衡的二叉搜索樹,其要求每個節點的左右子樹高度差的絕對值都不超過1,這 樣可以保證查詢時高效的時間復雜度,即$log_2 (N)$。但是如果要對AVL樹做一些結構修改的操作,性能非常低下,比如:插入時要維護其絕對平衡,旋轉的次數比較多,更差的是在刪除時, 有可能一直要讓旋轉持續到根的位置。因此:如果需要一種查詢高效且有序的數據結構,而且數 據的個數為靜態的(即不會改變),可以考慮AVL樹,但一個結構經常修改,就不太適合。