一、AVL樹的概念
二叉搜索樹雖然可以縮短查找的效率,但如果數據有序或接近有序,二叉搜索樹將退化爲單支樹,查找元素相當於在順序表中搜索元素,效率低。因此,兩位俄羅斯的數學家G.M.Adelson-Velskii和E.M.Landis在1962年發明了一種解決上述問題的方法:當向二叉搜索樹中插入新結點後,如果能保證每個結點的左右子樹高度之差的絕對值不超過1(需要對樹中的結點進行調整),即可降低樹的高度,從而減少平均搜索長度。
搜索二叉樹 == 排序二叉樹,因爲走它的中序遍歷的結果是有序的。
一棵AVL樹或者空樹,都有下面的性質
- 它的左右子樹都輸AVL樹
- 左右子樹的高度之差(簡稱平衡因子)的絕對值不超過1(-1/0/1)
平衡因子 = 右減左
如果一顆二叉搜索樹是高度平衡的,它就是AVL樹,如果它有n個節點,其高度可以保持在O(logN)
搜索時間複雜度O(log2n)
二、AVL樹的模擬實現
1 AVL樹的節點定義
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
std::pair<K, V> _kv;
int _bf;
};
2 AVL樹的插入
2.1 按照二叉搜索樹的方式插入新的節點
bool Insert(const std::pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
_root->_bf = 0;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur != nullptr)
{
if (cur->_kv.first < kv.first)
{
cur = parent;
cur = cur->_right;
}
else if (cur->_kv.first > kv.first)
{
cur = parent;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(kv);
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
//更新後,可能破壞了AVL樹的平衡性,所以需要更新平衡因子,並檢測是否破壞了AVL樹的平衡性。
2.2 更新平衡因子
2.3 AVL樹旋轉
如果在一顆樹原本是平衡的AVL樹中插入一個新節點,可能造成不平衡,此時必須調整樹的結構,讓它平衡。根據節點插入位置的不同,AVL樹的旋轉分爲四種:
2.3.1 新節點插入較高左子樹的左側–左左:右單旋
上圖在插入前,AVL樹是平衡的,新節點插入到30的左子樹(這不是左孩子)中,30左子樹增加了一層,導致以60爲根的二叉樹不平衡,要讓60平衡,只能把60的左子樹高度減少一層,右子樹增加一層。
也就是把左子樹往上提,這樣60轉下來,因爲60比30大,只能將其放在30的右子樹,而如果30有右子樹,右子樹根的值一定大於30,小於60,只能將其放在60的左子樹,旋轉完成後,更新節點的平衡因子就好了。
在旋轉時,有以下幾種情況要考慮:
1、30節點的右孩子可能存在,也可能不存在
2、60可能是根節點,也可能是子樹
如果是根節點,旋轉完成後,要更新根節點
如果是子樹,可能是某個節點的左子樹,也可能是右子樹
void RotateRight(Node* parent)//parent->_bf == -2 && cur->_bf == -1
{
Node* cur = parent->_left;
Node* right = cur->_right;//把cur的right給parent的左
if (right != nullptr)
{
parent->_left = right;
right->_parent = parent;
}
Node* node = parent->_parent;//先記錄一下parent原來的父節點
cur->_right = parent;//把parent給cur的右
parent->_parent = cur;//更新parent的父節點
//把cur與parent之前的父節點鏈接
if (parent == _root)//如果之前parent就是根,直接把根變成cur
{
_root = cur;
_root->_parent = nullptr;
}
else//如果之前的parent有父節點,那麼就要鏈接
{
if (node->_left == parent)
node->_left = cur;
else
node->_right = cur;
cur->_parent = node;
//node->_bf = 0;
//cur->_bf = 0;
}
}
2.3.2 新節點插入較高右子樹的右側–右右:左單旋
void RotateLeft(Node* parent)//parent->_bf == 2 && parent->_bf ==1
{
Node* cur = parent->_right;
Node* left = cur->_left;//把cur的left給parent的右
if (left != nullptr)
{
parent->_right = left;
left->_parent = parent;
}
Node* node = parent->_parent;//先記錄一下parent原來的父節點
cur->_left = parent;//把parent給cur的左
parent->_parent = cur;//更新parent的父節點
//把cur與parent之前的父節點鏈接
if (parent == _root)//如果parent就是之前的根,直接把根變成cur,並且把cur的_parent置空
{
_root = cur;
_root->_parent = nullptr;
}
else//如果之前的parent右父節點,就要鏈接
{
if (node->_left == parent)
node->_left = cur;
else
node->_right = cur;
cur->_parent = node;
}
}
2.3.3 新節點插入較高左子樹的右側–左右:先左單旋在右單旋
把雙旋變成單旋在旋轉,即:先對30進行左單旋,然後在對90進行右單旋,旋轉完成後在考慮平衡因子的更新
void RotateLR(Node* parent)//parent->_bf == -2 && cur->_bf == 1
{
RotateLeft(parent->_left);
RotateRight(parent);
}
2.3.4 新節點插入較高右子樹的左側–右左:先右單旋在左單旋
void RoataeRL(Node* parent)//parent->_bf == 2 && cur->_bf == -1
{
RotateRight(parent->_right);
RotateLeft(parent);
}
三、AVL樹的驗證
AVL樹是在二叉搜索樹的基礎加入了平衡性的限制,因此要驗證AVL樹,可以分兩步:
1、驗證其是二叉搜索樹
2、驗證其是平衡樹
- 每個節點的子樹高度差的絕對值不超過1
- 節點的平衡因子是否計算正確
int _Height(Node* pRoot)
{
if (nullptr == pRoot)
return 0;
// 計算pRoot左右子樹的高度
int leftHeight = _Height(pRoot->_pLeft);
int rightHeight = _Height(pRoot->_pRight);
// 返回左右子樹中較高的子樹高度+1
return (leftHeight > rightHeight) ? (leftHeight + 1) : (rightHeight + 1);
}
int Height()
{
_Height(_root);
}
bool _IsBalanceTree(Node* pRoot)
{
// 空樹也是AVL樹
if (nullptr == pRoot)
return true;
// 計算pRoot節點的平衡因子:即pRoot左右子樹的高度差
int leftHeight = _Height(pRoot->_pLeft);
int rightHeight = _Height(pRoot->_pRight);
int diff = rightHeight - leftHeight;
// 如果計算出的平衡因子與pRoot的平衡因子不相等,或者
// pRoot平衡因子的絕對值超過1,則一定不是AVL樹
if (diff != pRoot->_bf || (diff > 1 || diff < -1))
return false;
// pRoot的左和右如果都是AVL樹,則該樹一定是AVL樹
return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot->_pRight);
}
bool IsBalanceTree(Node* root)
{
_IsBalanceTree(_root);
}
驗證用例:{16, 3, 7, 11, 9, 26, 18, 14, 15}
四、AVL樹的性能
AVL樹是一顆絕對平衡的二叉搜索樹,其要求每個節點的左右子樹高度差的絕對值不超過1,這樣可以保證查詢時高效的時間複雜度,即log(N)。但是如果要對AVL樹做一些結構的修改的操作,性能非常低,比如:插入時要維護其絕對平衡,旋轉的次數比較多,更差的是在刪除是,有可能要一直旋轉到根的位置。因此需要一種查詢高效且有序的數據結構,而且數據的個數是靜態的,可以考慮AVL樹,但如果是經常涉及修改的場景,就不太適合。