AVL树
1、AVL树的概念
AVL树是最先发明的自平衡⼆叉查找树,AVL是一颗空树,或者具备下列性质的二叉搜索树:**它的左右子树都是AVL树,且左右子树的高度差的绝对值不超过1。**AVL树是⼀颗高度平衡搜索二叉树,通过控制高度差去控制平衡
如果一棵二叉搜索树是高度平衡的,它就是AVL树,AVL树整体节点数量和分布和完全二叉树类似,高度可以控制在 l o g N logN logN,那么增删查改的效率也可
以控制在 O ( l o g N ) O(logN) O(logN),相比二叉搜索树有了本质的提升。

2、AVL树节点的定义
cpp
template<class K, class V>
struct AVLTreeNode
{
// 需要parent指针,后续更新平衡因子可以看到
pair<K, V> _kv; //存储key和value
AVLTreeNode<K, V>* _left; //指向左孩子
AVLTreeNode<K, V>* _right; //指向右孩子
AVLTreeNode<K, V>* _parent; //指向父亲结点
int _bf; // balance factor 平衡因子
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{ }
};
3、AVL树的整体结构
cpp
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
private:
Node* _root = nullptr;
};
4、AVL树的插入
AVL树在二叉搜索树的基础上引入平衡因子,所以:
- AVL树插入一个值按二叉搜索树规则进行插入
- 调整节点的平衡因子
每个结点都有⼀个平衡因子,任何结点的平衡因子等于右子树的高度减去左子树的高度,也就是说任何结点的平衡因子等于0/1/-1,AVL树并不是必须要平衡因子,但是有了平衡因子可以更方便我们去进行观察和控制树是否平衡
4.1 平衡因子的更新
新节点插入后,AVL树的平衡性可能会遭到破坏,此时需要更新平衡因子,对AVL的平衡性进行检测
- 平衡因子 = 右子树高度 - 左子树高度
- 只有子树高度变化才会影响当前结点平衡因子
- parent所在子树的高度是否变化决定了是否会继续往上更新
- 如果节点插入到parent的左侧,只需要把parent的平衡因子 -1 即可
- 如果节点插入到parent的右侧,只需要把parent的平衡因子 +1 即可
更新后parent的平衡因子有三种情况:
- 更新后parent的平衡因子等于0,说明插入前parent的平衡因子一定为+1或-1,说明更新前parent子树⼀边高⼀边低,新增的节点插入在低的一边,插入后parent所在的子树高度不变,不会影响parent的父亲结点的平衡因子,更新结束
- 更新后parent的平衡因子是+1或-1,说明插入前的平衡因子是0,及插入前parent的子树两边一样高,新增的节点插入后parent所在的子树⼀边高一边低,parent所在的子树符合平衡要求,但是高度增加了1,会影响parent的父亲结点的平衡因子,所以要继续向上更新
- 更新后parent的平衡因子等于2 或 -2,不符合平衡树的性质,需要进行旋转处理
cpp
bool Insert(const pair<K, V>& kv)
{
// 这棵树为空,建立根结点
if (_root == nullptr)
{
//新建节点,存入要插入的键值对,赋值给根结点
_root = new Node(kv);
return true;
}
// 树不为空,循环寻找插入位置的父节点parent
// parent初始置空
Node* parent = nullptr;
// cur遍历指针,从根节点开始向下遍历二叉搜索树
Node* cur = _root;
while (cur)
{
// 当前节点的key < 待插入key → 新节点要插在右子树
if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
// 当前节点的key > 待插入key → 新节点要插在左子树
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
// 剩下情况:两个key相等,二叉搜索树不允许重复key,插入失败,返回false
else
{
return false;
}
}
// 循环结束后cur一定为空,parent为父节点
// 复用cur,储存kv的节点
cur = new Node(kv);
cur->_bf = 0; //平衡因子初始化为0
// 判断新节点是父亲的右孩子还是左孩子
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
// 新节点和父节点相连接,旋转操作需要用到父指针
cur->_parent = parent;
// 从父节点向上遍历所有祖先,更新平衡因子,失衡则旋转
// parent不为空,说明还有上层祖先需要更新bf,循环向上走
while (parent)
{
// 判断新节点cur是parent的右孩子还是左孩子,更新bf
if (cur == parent->_right)
{
// 新增右孩子 → 右子树高度+1,bf自增1
parent->_bf++;
}
else
{
// 新增左孩子 → 左子树高度-1,bf自减1
parent->_bf--;
}
//----------情况1:更新后平衡因子等于0--------------------
// 插入前parent bf是±1,插入后左右等高
if (parent->_bf == 0)
{
break; //跳出循环,不用再向上更新
}
// ---------情况2:更新后平衡因子为1 / -1-----------------
// 插入前parent bf是0,插入后子树高度+1,会影响上层祖先,必须继续向上更新
else if (parent->_bf == 1 || parent->_bf == -1)
{
// 继续往上更新
// 把当前parent赋值给cur:下一轮循环的"子节点"就是现在的父节点
cur = parent;
// parent向上跳一层,指向自己的父节点,下一轮循环处理更高层
parent = parent->_parent;
}
// ----情况3:更新后平衡因子为2/-2,树失衡,需要旋转修复------
else if (parent->_bf == 2 || parent->_bf == -2)
{
// 旋转处理
// 右单旋:parent bf=-2,左孩子cur bf=-1 → 单右旋
if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
// 左单旋:parent bf=2,右孩子cur bf=1 → 单左旋
else if (parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent);
}
// 左右双旋:parent bf=-2,左孩子cur bf=1 → 先左后右双旋
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
// 右左双旋:parent bf=2,右孩子cur bf=-1 → 先右后左双旋
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
// 理论上不会走到这里,走到说明逻辑出错
else
{
assert(false);
}
// 旋转修复后子树高度恢复原样,上层祖先不受影响,跳出循环
break;
}
// 兜底分支:合法AVL树节点bf只能是-1/0/1,走到这里说明插入前树就已经失衡损坏
else
{
// 插入之前这棵树就有2/-2 bf的节点,这棵树之前就不是AVL树
assert(false);
}
}
return true;
}
4.2 AVL树的旋转
旋转的原则:
- 保持搜索树的原则
- 让旋转的树从不平衡变平衡,其次降低旋转树的高度
旋转分为四种左单旋/右单旋/左右双旋/右左双旋
4.2.1 右单旋
新节点插入在左子树的左侧,左左失衡:即parent的平衡因子为-2,cur的平衡因子为-1,此时需要右单旋



旋转核心步骤,因为5<b子树的值<10,将b变成10的左子树,10变成5的右子树,5变成这棵树新的根,符合搜索树的规则,控制了平衡
cpp
void RotateR(Node* parent)
{
// 1. 取出parent的左孩子,命名subL
Node* subL = parent->_left;
// 2. 取出subL的右子树,命名subLR
Node* subLR = subL->_right;
// 第一步:断开subL和subLR,把subLR挂到parent左边
parent->_left = subLR;
// 如果subLR不是空树,就要反向设置它的父节点为parent(双向指针)
if (subLR)
subLR->_parent = parent;
// 3. 提前保存parent原来的父节点(上层节点,后面要让上层指向新根subL)
Node* parentParent = parent->_parent;
// 第二步:subL和parent互相连接,subL当parent的父节点
subL->_right = parent;
parent->_parent = subL;
//情况1:失衡节点parent是整棵树的根节点
if (parent == _root)
{
//整棵树的根更新为subL
_root = subL;
// 根节点无父节点,置空父节点
subL->_parent = nullptr;
}
// 情况2:parent不是根,存在上层父节点parentParent
else
{
// 如果parent是上层节点的左孩子
if (parentParent->_left == parent)
{
// 上层左分支改为指向新根subL
parentParent->_left = subL;
}
// 如果parent是上层节点的右孩子
else
{
// 上层右分支改为指向新根subL
parentParent->_right = subL;
}
// 新根subL的父指针绑定上层节点parentParent
subL->_parent = parentParent;
}
// 单右旋完成后,subL与parent两棵节点平衡因子都变为0
parent->_bf = subL->_bf = 0;
}
4.2.2 左单旋
新节点插入在右子树的右侧,右右失衡:即parent的平衡因子为2,cur的平衡因子为1,此时需要左单旋

旋转核心步骤,因为10 < b子树的值 < 15,将b变成10的右子树,10变成15的左子树,15变成这棵树新的根,符合搜索树的规则,控制了平衡
cpp
void RotateL(Node* parent)
{
// 把parent的右孩子设置为subR
Node* subR = parent->_right;
// 拿到subR自己的左子树subRL,这棵树等下要挪给parent
Node* subRL = subR->_left;
// 把subRL挂到parent的右指针上
parent->_right = subRL;
// 如果subRL不是空树,要双向绑定
if (subRL)
subRL->_parent = parent;
// 记录parent原来的父节点
Node* parentParent = parent->_parent;
// subR的左分支现在接parent,parent变成subR的左孩子
subR->_left = parent;
parent->_parent = subR;
// 分支1:如果原来失衡节点parent是整棵AVL树的根
if (parent == _root)
{
// 整棵树的根换成subR
_root = subR;
// 根节点没有父节点,父指针置空
subR->_parent = nullptr;
}
// 分支2:parent不是全局根,上面还有父节点parentParent
else
{
// 判断parent是上层节点的左孩子还是右孩子
if (parentParent->_left == parent)
{
// 上层节点左边原本是parent,现在替换成新根subR
parentParent->_left = subR;
}
else
{
// 上层节点右边原本是parent,现在替换成新根subR
parentParent->_right = subR;
}
// 新根subR的父指针绑定上层节点parentParent,和大树连在一起
subR->_parent = parentParent;
}
subR->_bf = parent->_bf = 0;
}
4.2.3 先左单旋再右单旋
新节点插入在较高左子树的右侧,左右失衡:即parent的平衡因子是-2,cur的平衡因子是1,此时需要先以cur为旋转点进行左单旋,再以parent为旋转点进行一个右单旋
图1:

图2:

图3:

cpp
void RotateLR(Node* parent)
{
// 提取两个关键子节点
Node* subL = parent->_left; // parent左孩子
Node* subLR = subL->_right; // subL右孩子
// 纪录旋转前subLR的平衡因子
int bf = subLR->_bf;
// 2. 两次单旋:先左旋subL,再右旋parent
RotateL(subL);
RotateR(parent);
// 3.根据原subLR的bf,修正三个节点平衡因子
if (bf == 0) //图1
{
// 场景:插入节点就是subLR本身
subL->_bf = 0;
parent->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 1) //图2
{
// 场景:新节点插在subLR的右子树
subL->_bf = -1;
parent->_bf = 0;
subLR->_bf = 0;
}
else if (bf == -1) //图3
{
// 场景:新节点插在subLR的左子树
subL->_bf = 0;
parent->_bf = 1;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
4.2.4 先右单旋再左单旋
新节点插入在较高右子树的左侧,右左失衡:即parent的平衡因子是2,cur的平衡因子是-1,此时需要先以cur为旋转点进行右单旋,再以parent为旋转点进行一个左单旋
图1:

图2:

图3:

cpp
void RotateRL(Node* parent)
{
// 1. subR:失衡节点parent的右孩子
Node* subR = parent->_right;
// 2. subRL:subR的左孩子,新增节点所在子树,双旋后成为新根
Node* subRL = subR->_left;
// 3. 提前保存subRL原始平衡因子,旋转会修改节点bf,必须先备份用于后续修正
int bf = subRL->_bf;
// 先进行右旋再进行左旋
RotateR(parent->_right);
RotateL(parent);
// 场景1:bf=0,新增节点就是subRL自身,三者旋转后左右高度全部相等
if (bf == 0) //图1
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}
// 场景2:bf=1,新增节点在subRL右子树,parent左子树更浅,平衡因子-1
else if (bf == 1) //图2
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
// 场景3:bf=-1,新增节点在subRL左子树,subR右子树更深,平衡因子1
else if (bf == -1) //图3
{
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
4.3 AVL树插入完整代码
cpp
public:
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->_right;
}
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(kv);
cur->_bf = 0;
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
while (parent)
{
if (cur == parent->_right)
{
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);
}
else
{
assert(false);
}
break;
}
else
{
// 插入之前这棵树就有2/-2 bf的节点,这棵树之前就不是AVL树
assert(false);
}
}
return true;
}
private:
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;
if (parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
{
parentParent->_left = subL;
}
else
{
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
parent->_bf = subL->_bf = 0;
}
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 (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
{
parentParent->_left = subR;
}
else
{
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
subR->_bf = parent->_bf = 0;
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(subL);
RotateR(parent);
if (bf == 0)
{
subL->_bf = 0;
parent->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 1)
{
subL->_bf = -1;
parent->_bf = 0;
subLR->_bf = 0;
}
else if (bf == -1)
{
subL->_bf = 0;
parent->_bf = 1;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
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);
}
}
}
4.4 AVL树的其他功能函数
InOrder:二叉树中序遍历
Find:根据key查找对应节点
IsBalanceTree:判断树是否平衡,检查平衡因子
Height:获取该树高度
Size:求二叉树节点的个数
注意:每个节点子树高度的绝对值不超过1
cpp
public:
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool IsBalanceTree()
{
return _IsBalanceTree(_root);
}
int Height()
{
return _Height(_root);
}
int Size()
{
return _Size(_root);
}
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:
int _Size(Node* root)
{
return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
}
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)
{
if (root == nullptr)
return true;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
int bf = rightHeight - leftHeight;
if (abs(bf) >= 2 || bf != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
return _IsBalanceTree(root->_left)
&& _IsBalanceTree(root->_right);
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << " ";
//cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
5、测试AVL树
cpp
int main()
{
AVLTree<int, int> t;
// 常规的测试用例
//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
// 特殊的带有双旋场景的测试用例
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto e : a)
{
if (e == 14)
{
int x = 0;
}
t.Insert({ e, e });
//t.InOrder();
cout << "Insert:" << e << "->" << t.IsBalanceTree() << endl;
}
t.InOrder();
cout << t.IsBalanceTree() << endl;
}
输出结果:
Insert:4->1
Insert:2->1
Insert:6->1
Insert:1->1
Insert:3->1
Insert:5->1
Insert:15->1
Insert:7->1
Insert:16->1
Insert:14->1
1 2 3 4 5 6 7 14 15 16
1