1 AVL树的概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度 O ( l o g 2 n ) O(log_2 n) O(log2n).
2 AVL树结点的定义
代码:
template<class K,class V>
class AVLNode
{
public:
AVLNode<K, V>* _left;
AVLNode<K, V>* _right;
AVLNode<K, V>* _parent;
pair<K, V> _kv;
int _bf;//balance factory (右边++,左边--)
public:
AVLNode(const pair<K, V> kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _bf(0)
{}
};
template<class K, class V>
class AVLTree
{
typedef AVLNode<K, V> Node;
private:
Node* _root=nullptr;
};
这个跟我们讲解的普通搜索二叉树的思路几乎是一样的,很容易理解。
3 AVL树的插入
这是我们今天要讲解的重点,也是难点。实现AVL树的方式有很多,博主采用的是平衡因子加三叉链这种方式来实现,因为相对于其他方式这种方式的理解要稍微简单些。
首先基本的框架搭建好:
bool insert(const pair<K, V> kv)
{
if (_root == nullptr)
{
_root = new Node(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 > kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
return true;
}
这个是我们讲解普通二叉搜索树玩剩下的,很好理解。
现在的关键是如何更新平衡因子?
我们不难发现:
- 当插入在parent左边时,平衡因子–,插入在parent右边时平衡因子++;
- 插入后parent的平衡因子为0,便不用向上更新了,如果为-1/1,便还要向上更新;
- 当parent的平衡因子为-2/2时说明已经出问题了,我们就要使出旋转大法修正,旋转完毕后便不用向上更新了。
代码解释:
//更新平衡因子
while (parent)
{
if (cur == parent->_left)
parent->_bf--;
else
parent->_bf++;
if (parent->_bf == 0)
break;
else if (parent->_bf == 1 || parent->_bf == -1)
{
parent = parent->_parent;
cur = cur->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//已经出错了,需要旋转处理
if (parent->_bf == -2 && cur->_bf == -1)
{
//右单旋
RotateR(parent);
}
else if (parent->_bf == 2 && cur->_bf == 1)
{
//左单旋
RotateL(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
//先右单旋,再左单旋
RotateRL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
//先左单旋,再右单旋
RotateLR(parent);
}
else
{
assert(false);
}
break;//旋转完毕已经平衡了,记得break出去
}
else
{
assert(false);
}
}
其中出错要旋转不外乎份4种情况:左单旋,右单旋,左右双旋,右左双旋
我们一个一个来看:
3.1 右单旋
抽象图是这样的:
看着也很好理解,这样旋转后30 60 的平衡因子都变成了0,整个树也是平衡的。
我画了一个具象图来帮助大家理解:
h==0时:
h==1时:
h==2时:
代码实现:
void RotateR(Node* parent)
{
Node* childL = parent->_left;
Node* childLR = childL->_right;
Node* grand = parent->_parent;
parent->_left = childLR;
if (childLR)
childLR->_parent = parent;
childL->_right = parent;
parent->_parent = childL;
//别忘了,还要链接grand与childL的关系
if (grand == nullptr)
{
_root = childL;
childL->_parent = nullptr;
}
else
{
if (grand->_left == parent)
grand->_left = childL;
else
grand->_right = childL;
childL->_parent = grand;
}
//更新平衡因子
parent->_bf = childL->_bf = 0;
}
3.2 左单旋
左单旋和右单旋类似,只是换了下位置,这里我就只给出抽象图了,不画具象图:
代码实现:
void RotateL(Node* parent)
{
Node* childR = parent->_right;
Node* childRL = childR->_left;
Node* grand = parent->_parent;
parent->_right = childRL;
if (childRL)
childRL->_parent = parent;
childR->_left =parent ;
parent->_parent = childR;
if (grand == nullptr)
{
_root = childR;
childR->_parent = nullptr;
}
else
{
if (grand->_left == parent)
grand->_left = childR;
else
grand->_right = childR;
childR->_parent = grand;
}
parent->_bf = childR->_bf = 0;
}
3.3 右左双旋
像下面这种情况,我们只是用单旋是解决不了问题的,要用双旋解决:
先以90结点右单旋转化成了我们前面讲的单旋问题,再以30左单旋即可,但是要注意分析插入后未旋转前新节点后60的平衡因子,因为60的平衡因子不同我们旋转后所更新结点的平衡因子就有所差异。注意插入后60的平衡因子可能为0.(这里建议大家画图分析)
代码实现:
void RotateRL(Node* parent)
{
Node* childR = parent->_right;
Node* childRL = childR->_left;
int bf = childRL->_bf;
RotateR(childR);
RotateL(parent);
//更新平衡因子
childRL->_bf = 0;
if (bf == -1)
{
childR->_bf = 1;
parent->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
childR->_bf = 0;
}
else if (bf == 0)
{
;
}
else
{
assert(false);
}
}
3.4 左右双旋
跟右左双旋类似,这里就不在多讲了:
代码实现:
void RotateLR(Node* parent)
{
Node* childL = parent->_left;
Node* childLR = childL->_right;
int bf = childLR->_bf;
RotateL(childL);
RotateR(parent);
//更新平衡因子
childLR->_bf = 0;
if (bf == -1)
{
childL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
parent->_bf = 0;
childL->_bf = -1;
}
else if (bf == 0)
{
;
}
else
{
assert(false);
}
}
4 AVL树的验证
代码:
private:
Node* _root=nullptr;
void _inorder(Node* root)
{
if (root == nullptr)
return;
_inorder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_inorder(root->_right);
}
size_t _height(Node* root)
{
if (root == nullptr)
return 0;
int leftHeight = _height(root->_left);
int rightHeight= _height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _isbalance(Node* root)
{
if (root == nullptr)
return true;
int leftHeight = _height(root->_left);
int rightHeight = _height(root->_right);
return (abs(leftHeight - rightHeight) <= 1) && _isbalance(root->_left) && _isbalance(root->_right);
}
public:
void inorder()
{
_inorder(_root);
}
size_t height()
{
return _height(_root);
}
bool isbalance()
{
return _isbalance(_root);
}
平衡二叉树的验证我们很早就讲过了,所以相信大家能够很轻易看懂代码。测试时还可以根据左右子树高度差来判断平衡因子的正确性,大家可以自己下去试试。
大家下去可以用一些随机数来测测。
有需要的老铁可以去博主码云里看看:
点击这里
好了,今天的分享就到这里了,我们下期再见。