AVL树的基本概念
AVL树是二叉搜索树的一种思想进阶,它的结构是在二叉搜索树的结构前提下形成的。
基本特点:
1.它的左右子树的高度相差最多为一
2.为了更好的控制左右子树的平衡,它引出了平衡因子(balance factor)的概念方便统计。每个结点都有⼀个平衡因⼦,任何结点的平衡因⼦等于右⼦树的⾼度减去左⼦树的⾼度,也就是说任何结点的平衡因⼦等于0/1/-1,AVL树并不是必须要平衡因⼦,但是有了平衡因⼦可以更⽅便我们去进⾏观察和控制树是否平衡,就像⼀个⻛向标⼀样。
3.AVL树整体结点数量和分布和完全⼆叉树类似,⾼度可以控制在log(N) ,那么增删查改的效率也可以控制在log(N),相⽐⼆叉搜索树有了本质的提升AVL树的实现
在这里我们只实现AVL树的插入,能让我们理解这个树的结构的形成,删除这里不做实现,因为我们学习树的这种复杂结构本身难度就大,其实只要你会了插入就差不多能写出删除。有兴趣的可以去尝试
它的基本框架
cpp
//AVL树的左右高度差最大为1;
//平衡因子= 右子树高度-左子树高度
template <class K, class V>
struct AVLTreeNode
{
pair<K, V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent; //寻找父结点
int _bf; //平衡因子,
AVLTreeNode(const pair<K,V>& key_value= pair<K,V>())
:_kv(key_value)
,_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
{}
};
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
//...
AVL树的插入实现
基本的插入跟我们的二叉搜索树的思想相同
cpp
bool insert(const pair<K, V>& key_value)
{
if (!_root)
{
_root = new node(key_value);
return true;
}
node* cur = _root;
node* parent = nullptr;
while (cur)
{
if (key_value.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (key_value.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else {
return false;
}
}
cur = new node(key_value);
if ( key_value.first < parent->_kv.first)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
只是做完前面的逻辑之后,还需要考虑平衡因子是否是再-1/0/1这几个数,所以还要从插入的位置直到开始的根结点,判断每个结点的平衡因子是否为正确的范围,如果不在就要进行旋转处理
首先以这张图为例,如果是插入的结点为7,然后使它的父结点平衡因子为0,因为是在它的右边插入,又因为平衡因子=右子树高度-左子树高度,所以说明它以前为1,那它的祖先不受影响,因为没有改变它们的高度,
所以第一种情况: 插入结点之后如果插入结点的父结点平衡因子为0,就不用再向上检查它的祖先
很快可以以得出第二种情况: 如果插入结点之后如果它的父结点为1或-1那就证明也会影响它们的祖先结点,那就要再向上去判断它的祖先结点的平衡因子是否平衡,直到最后的根结点如果是这种情况,向上判断有父结点平衡因子为2/-2的情况那就要旋转处理,将这个父结点的高度降下来,并且旋转之后不需要再向上判断
右旋转
特点: 由上图可知插入的-3为左边,那就构成了存粹的左边高,那就要进行右旋转,我们把它们转为抽象图表示更直观
操作为: 因为5<b<10,那我们可以将b作为10的左子树,将10作为5的右子树,这样5就成了新的部分树的根结点或者就是根结点,要看10是否有祖先,这样左右就平衡了
然后只需注意如果b不为空,b的_parent指针要指向新的父结点,以前接受的旋转的parent结点也就是10要更新它的parent,5的也要更新,并且如果10有父结点,那5的parent指针也要更新,最后要更新它们的平衡因子
代码实现:
cpp
void RotateR(node*& parent)
{
//先旋转
node* subL = parent->_left;
node* subLR = subL->_right;
parent->_left = subLR;
node* parent_parent = parent->_parent;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
parent->_parent = subL;
//再处理旋转后parent的问题
if (parent_parent)
{
if (parent_parent->_left == parent)
{
parent_parent->_left = subL;
}
else {
parent_parent->_right = subL;
}
subL->_parent = parent_parent;
}
else {
_root = subL;
subL->_parent = nullptr;
}
//最后处理平衡因子的问题
subL->_bf = parent->_bf = 0;
}
左旋转
有了右旋转的认识,左旋转就是存粹的要平衡的结点的右边高,因为5<b<10,所以b可以当5的右子树·,5可以当10的左子树,这样就平衡了,最后更新它们的parent结点和平衡因子
代码实现:
cpp
void RotateL(node*& parent)
{
node* subR = parent->_right;
node* subRL = subR->_left;
node* parent_parent = parent->_parent;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
parent->_parent = subR;
if (parent_parent)
{
if (parent_parent->_left == parent)
{
parent_parent->_left = subR;
subR->_parent = parent_parent;
}
else {
parent_parent->_right = subR;
subR->_parent = parent_parent;
}
}
else {
_root = subR;
subR->_parent = nullptr;
}
subR->_bf = parent->_bf = 0;
}
左右双旋
对比只要左旋转,这个就不是存粹的左边高,它是在5的右边,你如果就只左旋转的话是不行的,所以需要在b的基础上再分出父结点和左右子树
左边⾼时,如果插⼊位置不是在a⼦树,⽽是插⼊在b⼦树,b⼦树⾼度从h变成h+1,引发旋转,右单旋⽆法解决问题,右单旋后,我们的树依旧不平衡。右单旋解决的纯粹的左边⾼,但是插⼊在b⼦树中,10为跟的⼦树不再是单纯的左边⾼,对于10是左边⾼,但是对于5是右边⾼,需要⽤两次旋转才能解决,以5为旋转点进⾏⼀个左单旋,以10为旋转点进⾏⼀个右单旋,这棵树就平衡了。然后将以5为一小部分要旋转的子树,将5和8进行左旋转,将树弄为存粹左边高,但是插入的数据可能在e上也可能为f上,但这没关系,最终影响的是平衡因子先将结构弄好在考虑,我这里展示的就放e上
然后再进行右旋转
最后更新平衡因子,在f的位置时,在原有的基础上改平衡因子就行
代码如下:
cpp
void RotateLR(node* parent)
{
node* subL = parent->_left;
node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(subL);
RotateR(parent);
//调整平衡因子
if (bf == 0)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 0;
}
else if (bf == -1)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
subLR->_bf = 0;
subL->_bf = -1;
parent->_bf = 0;
}
else {
assert(false);
}
}
右左双旋
跟左右双旋类似,下⾯我们将a/b/c⼦树抽象为⾼度h的AVL⼦树进⾏分析,另外我们需要把b⼦树的细节进⼀步展开为12和左⼦树⾼度为h-1的e和f⼦树,因为我们要对b的⽗亲15为旋转点进⾏右单旋,右单旋需要动b树中的右⼦树。b⼦树中新增结点的位置不同,平衡因⼦更新的细节也不同,通过观察12的平衡因⼦不同,这⾥我们要分三个场景讨论。
•
场景1:h>=1时,新增结点插⼊在e⼦树,e⼦树⾼度从h-1变为h并不断更新12->15->10平衡因⼦,引发旋转,其中12的平衡因⼦为-1,旋转后10和12平衡因⼦为0,15平衡因⼦为1。
•场景2:h>=1时,新增结点插⼊在f⼦树,f⼦树⾼度从h-1变为h并不断更新12->15->10平衡因⼦,引发旋转,其中12的平衡因⼦为1,旋转后15和12平衡因⼦为0,10平衡因⼦为-1。
• 场景3:h==0时,a/b/c都是空树,b⾃⼰就是⼀个新增结点,不断更新15->10平衡因⼦,引发旋转,其中12的平衡因⼦为0,旋转后10和12和15平衡因⼦均为0。代码如下:
cpp
void RotateRL(node* parent)
{
node* subR = parent->_right;
node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(subR);
RotateL(parent);
//更新平衡因子
if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else {
assert(false);
}
}
平衡检测
cpp
int _Height(node* 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(node* root)
{
if (root == nullptr)
{
return true;
}
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
int diff = rightHeight - leftHeight;
if (abs(diff) >= 2 || diff != root->_bf)
{
cout << root->_kv.first << "->" << " 平衡因子异常";
return false;
}
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}











