AVL树的概念
AVL树是最先发明的平衡二叉树,她是一棵空树或者具备以下性质的二叉搜索树:
- 左右子树都是二叉搜索树
- 左右子树的高度差的绝对值不超过1
AVL树是一棵高度平衡的二叉搜索树通过控制高度差去控制平衡。
在AVL树中我们需要引入应该概念:平衡因子。每个平衡因子等同于右子树的高度减去左子树的高度,也就是任何结点的平衡因子等于0/1/-1。
那为什么不控制高度差为0呢?这样二叉树不是更平衡吗?答案很简单,做不到,假设一个数为两个结点或者为4个结点,此时最好的平衡状态就是高度差为1。
AVL树的实现:
cpp
template<class K, class V>
struct AVLTreeNode
{
std::pair<k, V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf;
AVLTreeNode(const std::pair<K,V>& kv)
:_kv(kv)
,_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
{}
};
AVL树的插入
AVL树的插入大概遵循以下过程:
- 按照二叉搜索树的规则进行插入数值
- 新增结点之后,会影响祖先结点的高度,也就是可能影响部分祖先结点的平衡因子,所以更新从新增结点->根节点路劲上的平衡因子,最坏情况要更新到根节点,有些情况更新到中间结点即可。
- 更新平衡因子过程中没有出现问题,插入结束。
- 更新过程中出现不平衡,对不平衡的子树旋转,旋转后调平衡的同时,本质上就是降低了子树的高度,不会影响上一层,插入结束。
平衡因子更新
平衡因子 = 右子树高度 - 左子树高度
只有子树高度变化才会影响当前结点平衡因子。插入节点会增加高度,所以新增节点在parent的右子树时,parent的平衡因子++,在左子树时,parent的平衡因子--。parent所在子树的高度是否变化决定了是否会继续向上更新。
更新停止条件:
- 更新后
parent的平衡因⼦等于0,更新中parent的平衡因⼦变化为 -1->0或者1->0,说明更新前parent⼦树⼀边⾼⼀边低,新增的结点插⼊在低的那边,插⼊后parent所在的⼦树⾼度不变,不会影响parent的⽗亲结点的平衡因⼦,更新结束。 - 更新后parent的平衡因⼦等于1或 - 1,更新前更新中
parent的平衡因⼦变化为0->1或者0-> - 1,说明更新前parent⼦树两边⼀样⾼,新增的插⼊结点后,parent所在的⼦树⼀边⾼⼀边低,parent所在的⼦树符合平衡要求,但是⾼度增加了1,会影响parent的⽗亲结点的平衡因⼦,所以要继续向上更新。 - 更新后
parent的平衡因⼦等于2或 - 2,更新前更新中parent的平衡因⼦变化为1->2或者- 1-> - 2,说明更新前parent⼦树⼀边⾼⼀边低,新增的插⼊结点在⾼的那边,parent所在的⼦树⾼的那边更⾼了,破坏了平衡,parent所在的⼦树不符合平衡要求,需要旋转处理, 旋转的⽬标有两个:1、把parent⼦树旋转平衡。2、降低parent⼦树的⾼度,恢复到插⼊结点以前的⾼度。所以旋转后也不需要继续往上更新,插⼊结束。 - 不断更新,更新到根,跟的平衡因⼦是1或 - 1也停⽌了。
cpp
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
bool Insert(const std::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->_right;
}
else if(cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_left;
}
else return false;
}
cur = new Node(kv);
if(parent->_kv.first < kv.first)
parent->_right = cur;
else
parent->_left = cur;
cur->_parent = parent;
while(parent)
{
if(cur == parent->_left)
parent->_bf--;
else parent->_bf++;
if(parent->_bf == 0) break;
else if(parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_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)
{
RotateLR(parent);
}
else if(parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
break;
}
else assert(false);
}
return true;
}
private:
Node* _root = nullptr;
};
旋转
旋转的原则:
- 保持树的规则
- 让旋转的树从不满足变平衡,其次降低旋转树的高度
旋转总共分为四种,右单旋 / 左单旋 / 左右双旋 / 右左双旋。
右单旋

上图展示的是以10为根的树,有a / b / c抽象为三棵⾼度为h的⼦树(h >= 0),a / b / c均符合AVL树的要求(10可能是整棵树的根,也可能是⼀个整棵树中局部的⼦树的根)。这里a / b / c是三颗高度为h的子树,是一种抽象的概括表示,代表了所有右单旋的场景,由于情况是在非常多,这里我们不一一列举了。(5和10可更改为任意数,只要保证满足AVL树规则即可)
旋转核心步骤:因为 5 < b子树的值 < 10,将b变成10的左子树,10变成5的右子树,5变成这颗树新的根,符合搜索树的规则,控制了平衡,同时这棵树的高度恢复到了插入之前的h+2,符合旋转原则。如果插入之前10整棵树的一个局部子树,旋转后不会再影响上一层,插入就结束了。
cpp
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//需要注意除了要修改孩⼦指针指向,还是修改⽗亲
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* parentParent = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
// parent有可能是整棵树的根,也可能是局部的⼦树
//如果是整棵树的根,要修改_root
//如果是局部的指针要跟上⼀层链接
if (parentParent == nullptr)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (parent == parentParent->_left)
{
parentParent->_left = subL;
}
else
{
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
parent->_bf = subL->_bf = 0;
}
private:
Node* _root = nullptr;
};
左单旋

上图展示的依然是以10为根的树,有a / b / c抽象为三颗高度为h的子树,a / b / c均符合AVL树的要求,代表了所有左单旋的场景,实际左单旋形态有很多种,具体和上面右旋类似。
在a⼦树中插⼊⼀个新结点,导致a⼦树的⾼度从h变成h + 1,不断向上更新平衡因⼦,导致10的平衡因⼦从1变成2,10为根的树左右⾼度差超过1,违反平衡规则。10为根的树右边太⾼了,需要往左边旋转,控制两棵树的平衡。
旋转核心步骤:因为10 < b⼦树的值 < 15,将b变成10的右⼦树,10变成15的左⼦树,15变成这棵树新的根,符合搜索树的规则,控制了平衡,同时这棵的⾼度恢复到了插⼊之前的h + 2,符合旋转原则。如果插⼊之前10整棵树的⼀个局部⼦树,旋转后不会再影响上⼀层,插⼊结束了。
cpp
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* parentParent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (parentParent == nullptr)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parent == parentParent->_left)
{
parentParent->_left = subR;
}
else
{
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
parent->_bf = subR->_bf = 0;
}
private:
Node* _root = nullptr;
};
左右双旋

从上图可以看出,如果左边高时,插入位置不在a子树,而是插在b子树,b的高度从h变为h+1旋转,但右旋无法解决问题,右旋后,我们树依然不平衡。右单旋解决的纯粹的左边⾼,但是插⼊在b⼦树中,10为跟的⼦树不再是单纯的左边⾼,对于10是左边⾼,但是对于5是右边⾼,需要⽤两次旋转才能解决,以5为旋转点进⾏⼀个左单旋,以10为旋转点进⾏⼀个右单旋,这棵树这棵树就平衡了。
下⾯我们将a / b / c⼦树抽象为⾼度h的AVL⼦树进⾏分析,另外我们需要把b⼦树的细节进⼀步展开为8和左⼦树⾼度为h - 1的e和f⼦树,因为我们要对b的⽗亲5为旋转点进⾏左单旋,左单旋需要动b树中的左⼦树。b⼦树中新增结点的位置不同,平衡因⼦更新的细节也不同,通过观察8的平衡因⼦不同,这⾥我们要分三个场景讨论。
- 场景1:h >= 1时,新增结点插⼊在e⼦树,e⼦树⾼度从h - 1并为h并不断更新8->5->10平衡因⼦,引发旋转,其中8的平衡因⼦为 - 1,旋转后8和5平衡因⼦为0,10平衡因⼦为1。
- 场景2:h >= 1时,新增结点插⼊在f⼦树,f⼦树⾼度从h - 1变为h并不断更新8->5->10平衡因⼦,引发旋转,其中8的平衡因⼦为1,旋转后8和10平衡因⼦为0,5平衡因⼦为 - 1。
- 场景3:h == 0时,a / b / c都是空树,b⾃⼰就是⼀个新增结点,不断更新5->10平衡因⼦,引发旋转,其中8的平衡因⼦为0,旋转后8和10和5平衡因⼦均为0。
cpp
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == 0)
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 0;
}
else if (bf == -1)
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
subL->_bf = -1;
subLR->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
private:
Node* _root = nullptr;
};
右左双旋

跟左右双旋类似,下⾯我们依然将a / b / c⼦树抽象为⾼度h的AVL⼦树进⾏分析。(因为情况和上面差距不是特别大,因此笔者这里偷个小懒,就不将完整的变换图全画出来了)我们依然需要分为三个场景进行讨论:
- 场景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
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == 0)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}
else if (bf == 1)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
private:
Node* _root = nullptr;
};
AVL树的查找
AVL树的查找和二叉搜索树是一样的,都遵循相同的规则,因此笔者这里不再过多赘述,有疑问的可以移步二叉搜索树简述。查找效率为logN.
cpp
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < key)
{
cur = cur->_right;
}
else if (cur->_kv.first > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
private:
Node* _root = nullptr;
};
AVL树的平衡检查
对AVL树平衡检查只需要检查每个结点的高度差也就是检查平衡因子即可。
cpp
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
int _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 _IsBalanceTree(Node* root)
{
//空树也是AVL树
if (nullptr == root)
return true;
//计算pRoot结点的平衡因⼦:即pRoot左右⼦树的⾼度差
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
int diff = rightHeight - leftHeight;
//如果计算出的平衡因⼦与pRoot的平衡因⼦不相等,或者
// pRoot平衡因⼦的绝对值超过1,则⼀定不是AVL树
if (abs(diff) >= 2)
{
cout << root->_kv.first << "⾼度差异常" << endl;
return false;
}
if (root->_bf != diff)
{
cout << root->_kv.first << "平衡因⼦异常" << endl;
return false;
}
// pRoot的左和右如果都是AVL树,则该树⼀定是AVL树
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
private:
Node* _root = nullptr;
};
谢谢阅读!