AVL 樹概念
二叉搜索樹雖可以縮短查找的效率,但如果數據有序或接近有序二叉搜索樹將退化爲單支樹,查找元素相當於在順序表中搜索元素,效率低下
當向二叉搜索樹中插入新結點後,保證每個結點的左右子樹高度之差的絕對值不超過1(需要對樹中的結點進行調整),降低樹的高度,從而減少平均搜索長度,這樣的樹即 AVL 樹
一棵AVL樹具有以下性質:
- 它的左右子樹都是AVL樹
- 左右子樹高度之差(簡稱平衡因子)的絕對值不超過1
- AVL樹有n個結點,其高度可保持在 O(log N) ,搜索時間複雜度 O(log N)
AVL 樹實現
AVL 樹定義
左孩子置空、右孩子置空、父親節點置空、平衡因子置 0
AVL 樹插入
- 插入(先按照二叉搜索樹的規律插入)
- 更新平衡因子
- 判斷平衡因子
a:平衡因子爲 0,停止更新,插入結束
b:平衡因子爲 1 / -1,繼續向上更新
c:平衡因子爲 2 / -2,則pParent的平衡因子違反平衡樹的性質,需要對其進行旋轉處理
旋轉操作:
新節點插入較高左子樹的左側—左左:右單旋
新節點插入較高右子樹的右側—右右:左單旋
新節點插入較高左子樹的右側—左右:先左單旋再右單旋
新節點插入較高右子樹的左側—右左:先右單旋再左單旋
AVL樹驗證
AVL樹是在二叉搜索樹的基礎上加入了平衡性的限制,因此要驗證AVL樹,可以分兩步:
- 驗證其爲二叉搜索樹
如果中序遍歷可得到一個有序的序列,就說明爲二叉搜索樹 - 驗證其爲平衡樹
每個節點子樹高度差的絕對值不超過1(注意節點中如果沒有平衡因子)
節點的平衡因子是否計算正確
AVL 樹代碼
#include <iostream>
#include <assert.h>
using namespace std;
template<class T>
struct AVLNode
{
AVLNode<T>* _left;
AVLNode<T>* _right;
AVLNode<T>* _parent;
T _value;
//平衡因子
int _bf;
AVLNode(const T& value = T())
: _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _value(value)
, _bf(0)
{}
};
template<class T>
class AVLTree
{
public:
typedef AVLNode<T> Node;
typedef Node* pNode;
void RotateR(pNode parent)//右旋
{
PNode pSubL = parent->_left;
PNode pSubLR = pSubL->_right;
parent->_left = pSubLR;
if (pSubLR)
pSubLR->_parent = parent;
pSubL->_right = parent;
PNode pParent = parent->_parent;
parent->_parent = pSubL;
pSubL->_parent = pParent;
if (NULL == pParent)
{
_root = pSubL;
pSubL->_parent = NULL;
}
else
{
if (pParent->_left == parent)
pParent->_left = pSubL;
else
pParent->_right = pSubL;
}
// 根據調整後的結構更新部分節點的平衡因子
parent->_bf = pSubL->_bf = 0;
}
void RotateL(pNode parent)//左旋
{
PNode pSubR = parent->_right;
PNode pSubRL = pSubR->_left;
parent->_right = pSubRL;
if (pSubRL)
pSubRL->_parent = parent;
pSubR->_left = parent;
PNode pParent = parent->_parent;
parent->_parent = pSubR;
pSubR->_parent = pParent;
if (NULL == pParent)
{
_root = pSubR;
pSubR->_parent = NULL;
}
else
{
if (pParent->_right == parent)
pParent->_right = pSubR;
else
pParent->_left = pSubR;
}
// 根據調整後的結構更新部分節點的平衡因子
parent->_bf = pSubL->_bf = 0;
}
void RotateLR(PNode pParent) //左右
{
PNode pSubL = pParent->_left;
PNode pSubLR = pSubL->_right;
// 旋轉之前,保存pSubLR的平衡因子,旋轉完成之後,需要根據該平衡因子來調整其他節點的平衡因子
int bf = pSubLR->_bf;
// 先進行左單旋
RotateL(pParent->_pLeft);
// 再進行右單旋
RotateR(pParent);
if (1 == bf)
pSubL->_bf = -1;
else if (-1 == bf)
pParent->_bf = 1;
}
void RotateRL(PNode pParent)//右左
{
PNode pSubR = pParent->_right;
PNode pSubRL = pSubR->_left;
// 旋轉之前,保存pSubLR的平衡因子,旋轉完成之後,需要根據該平衡因子來調整其他節點的平衡因子
int bf = pSubRL->_bf;
// 先進行右單旋
RotateR(pParent->_left);
// 再進行左單旋
RotateL(pParent);
if (1 == bf)
pSubR->_bf = -1;
else if (-1 == bf)
pParent->_bf = 1;
}
bool insert(const T& value)
{
// 1. 先按照二叉搜索樹的規則將節點插入到AVL樹中
// 如果樹爲空,直接插入
if (nullptr == _root)
{
_root = new pNode(data);
return true;
}
// 按照二叉搜索樹的性質查找data在樹中的插入位置
pNode pCur = _root;
// 記錄pCur的雙親,因爲新元素最終插入在pCur雙親左右孩子的位置
pNode pParent = nullptr;
while (pCur)
{
pParent = pCur;
if (data < pCur->_value)
pCur = pCur->_left;
else if (data > pCur->_value)
pCur = pCur->_right; // 元素已經在樹中存在
else
return false;
}
// 插入元素
pCur = new pNode(data);
if (data < pParent->_value)
pParent->_left = pCur;
else
pParent->_right = pCur;
// 2. 新節點插入後,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的平衡因子違反平衡樹的性質,需要對其進行旋轉處理
*/
while (pParent)
{
// 更新雙親的平衡因子
if (pCur == pParent->_pLeft)
pParent->_bf--;
else
pParent->_bf++;
// 更新後檢測雙親的平衡因子
if (0 == pParent->_bf)
break;
else if (1 == pParent->_bf || -1 == pParent->_bf)
{
// 插入前雙親的平衡因子是0,插入後雙親的平衡因爲爲1 或者 -1 ,說明以雙親爲根的二叉
// 樹的高度增加了一層,因此需要繼續向上調整
pCur = pParent;
pParent = pCur->_pParent;
}
else
{
// 雙親的平衡因子爲正負2,違反了AVL樹的平衡性,需要對以pParent
// 爲根的樹進行旋轉處理
if (2 == pParent->_bf && pCur->_bf == 1)
{
RotateL(pParent);
}
else if (2 == pParent->_bf && pCur->_bf == -1)
{
RotateRL(pParent);
}
else if (-2 == pParent->_bf && pCur->_bf == 1)
{
RotateLR(pParent);
}
else
{
RotateR(pParent);
}
}
}
return true;
}
//中序
void _InOrder(pNode root)
{
if (root)
{
_InOrder(root->_left);
cout << root->_value << " ";
_InOrder(root->_right);
}
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
int _Height(PNode root)
{
if (root == nullptr)
return 0;
int left = _Height(root->_left);
int right = _Height(root->_right);
return left > right ? left + 1 : right + 1;
}
//判斷平衡
bool _IsBalanceTree(pNode root)
{
// 空樹也是AVL樹
if (nullptr == root)
return true;
// 計算root節點的平衡因子:即root左右子樹的高度差
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
int diff = rightHeight - leftHeight;
// 如果計算出的平衡因子與root的平衡因子不相等,或者
// root平衡因子的絕對值超過1,則一定不是AVL樹
if (diff != root->_bf || (diff > 1 || diff < -1))
return false;
// pRoot的左和右如果都是AVL樹,則該樹一定是AVL樹
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
private:
pNode _root = nullptr;
};
void test()
{
AVLTree<int>* avl = new AVLTree<int>();
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
avl->insert(arr[i]);
}
}
int main()
{
test();
return 0;
}
AVL 樹優缺點分析
AVL樹是一棵絕對平衡的二叉搜索樹,其要求每個節點的左右子樹高度差的絕對值都不超過1,這樣可以保證查詢時高效的時間複雜度,即 。但是如果要對AVL樹做一些結構修改的操作,性能非常低下,比如:
插入時要維護其絕對平衡,旋轉的次數比較多,更差的是在刪除時,有可能一直要讓旋轉持續到根的位置。
因此:如果需要一種查詢高效且有序的數據結構,而且數據的個數爲靜態的(即不會改變),可以考慮AVL樹,但一個結構經常修改,就不太適合