平衡二叉树AVL
在多次插入和删除操作后,二叉搜索树可能退化为链表。在这种情况下,所有操作的时间复杂度将从 O(logn) 劣化为 O(n) 。如图所示,经过两次删除节点操作,这棵二叉搜索树便会退化为链表。
再例如,在图所示的完美二叉树中插入两个节点后,树将严重向左倾斜,查找操作的时间复杂度也随之劣化。
判断条件
- 是「二叉排序树」
- 任何一个节点的左子树或者右子树都是「平衡二叉树」(左右高度差小于等于 1)
相关概念
平衡因子 BF
定义:左子树和右子树高度差
计算:左子树高度 - 右子树高度的值
别名:简称 BF(Balance Factor 而不是 Boy Friend)
一般来说 BF 的绝对值大于 1,,平衡树二叉树就失衡,需要「旋转」纠正
获取平衡因子
/* 获取平衡因子 */
int balanceFactor(TreeNode *node) {
// 空节点平衡因子为 0
if (node == nullptr)
return 0;
// 节点平衡因子 = 左子树高度 - 右子树高度
return height(node->left) - height(node->right);
}
最小不平衡子树
距离插入节点最近的,并且 BF 的绝对值大于 1 的节点为根节点的子树。
「旋转」纠正只需要纠正**「最小不平衡子树」**即可
节点类
由于 AVL 树的相关操作需要获取节点高度,因此我们需要为节点类添加 height
变量:
/* AVL 树节点类 */
struct TreeNode {
int val{}; // 节点值
int height = 0; // 节点高度
TreeNode *left{}; // 左子节点
TreeNode *right{}; // 右子节点
TreeNode() = default;
explicit TreeNode(int x) : val(x){}
};
获取和更新高度
//获取高度
int height(TreeNode* node)
{
return node == nullptr ? -1 : node->height;
}
//更新节点高度
void updateHeight(TreeNode* node)
{
// 节点高度等于最高子树高度 + 1
node->height = max( height(node->left), height(node->right)) + 1;
}
旋转
2 种「旋转」方式:
- 左旋
- 旧根节点为新根节点的左子树
- 新根节点的左子树(如果存在)为旧根节点的右子树
- 右旋:
- 旧根节点为新根节点的右子树
- 新根节点的右子树(如果存在)为旧根节点的左子树
4 种「旋转」纠正类型:
- LL 型:插入左孩子的左子树,右旋
- RR 型:插入右孩子的右子树,左旋
- LR 型:插入左孩子的右子树,先左旋,再右旋
- RL 型:插入右孩子的左子树,先右旋,再左旋
如图
四种旋转情况的选择条件
失衡节点的平衡因子 | 子节点的平衡因子 | 旋转方法 |
---|---|---|
> 1(左偏树) | >=0 | 右旋 |
> 1(左偏树) | <0 | 先左旋后右旋 |
< -1(右偏树) | <=0 | 左旋 |
< -1(右偏树) | > 0 | 先右旋后左旋 |
代码:
//右旋操作
TreeNode* rightRotate(TreeNode* node) {
TreeNode* child = node->left;
if (child->right != nullptr)
{
TreeNode* grandChild = child->right;
child->right = node;
node->left = grandChild;
}
else
{
child->right = node;
}
updateHeight(node);
updateHeight(child);
return child;
}
//左旋操作
TreeNode* leftRotate(TreeNode* node) {
TreeNode* child = node->right;
if (child->left != nullptr) {
TreeNode* grandChild = child->left;
child->left = node;
node->right = grandChild;
}
else
{
child->left = node;
}
updateHeight(node);
updateHeight(child);
return child;
}
旋转的选择
//旋转的选择
TreeNode* rotate(TreeNode* node) {
int balfactor = balanceFactor(node);
if (balfactor > 1)
{
//左偏树
if (balanceFactor(node->left) >= 0) return rightRotate(node);
else {
node->left = leftRotate(node->left);
return rightRotate(node);
}
}
if (balfactor < -1)
{
//右偏树
if (balanceFactor(node->right) <= 0) return leftRotate(node);
else {
node->right = rightRotate(node->right);
return leftRotate(node);
}
}
return node;
}
插入节点
AVL 树的节点插入操作与二叉搜索树在主体上类似。唯一的区别在于,在 AVL 树中插入节点后,从该节点到根节点的路径上可能会出现一系列失衡节点。因此,我们需要从这个节点开始,自底向上执行旋转操作,使所有失衡节点恢复平衡。代码如下所示:
TreeNode* AvlTree::insertHelper(TreeNode* node, int val) {
if (node == nullptr)
return new TreeNode(val);
/* 1. 查找插入位置并插入节点 */
if (val < node->val)
node->left = insertHelper(node->left, val);
else if (val > node->val)
node->right = insertHelper(node->right, val);
else
return node; // 重复节点不插入,直接返回
updateHeight(node); // 更新节点高度
/* 2. 执行旋转操作,使该子树重新恢复平衡 */
node = rotate(node);
// 返回子树的根节点
return node;
}