目录
[1. AVL的概念编辑](#1. AVL的概念编辑)
[2. AVL 树的实现](#2. AVL 树的实现)
[2.1 AVL 树的结构](#2.1 AVL 树的结构)
[2.2 AVL 树的插入](#2.2 AVL 树的插入)
[2.3 旋转](#2.3 旋转)
[2.4 AVL 树的查找](#2.4 AVL 树的查找)
[2.5 AVL 树的平衡检查](#2.5 AVL 树的平衡检查)
[2.6 AVL树的删除](#2.6 AVL树的删除)
1. AVL的概念
AVL 树由前苏联数学家 G.M. Adelson-Velsky(阿德尔森 - 韦尔斯基) 和 E.M. Landis(兰迪斯) 在 1962 年提出并发表论文,它也是世界上第一个自平衡二叉搜索树。
AVL 和普通二叉搜索树(BST)最大区别
AVL 本质上依然是一棵二叉搜索树,保留 BST 的有序性质;额外增加一条平衡约束 :树中任意节点,左右子树高度差绝对值不超过 1(平衡因子 ∈ {-1,0,1})。一旦插入、删除造成失衡,会通过旋转自动调整结构、恢复平衡;普通 BST 没有这个自动平衡机制,树形态完全由插入顺序决定。
AVL 树进步的地方
- 操作复杂度稳定:查找、插入、删除最坏时间复杂度稳定 \(O(\log n)\),不会退化成链表,消除了 BST 最坏 \(O(n)\) 的风险。
- 树高被严格限制,树更 "矮",查找速度很快,适合读多写少的场景。
- 自平衡机制:增删后自动旋转修复平衡,不需要人工重构整棵树。
AVL 树的缺点
- 平衡条件很严格,插入、删除很容易触发旋转,尤其是删除操作,可能需要沿着路径向上多次旋转,调整开销大、写操作代价高。
- 每个节点需要额外记录子树高度(或者平衡因子),占用额外存储空间。
- 旋转逻辑多(LL、RR、LR、RL 四种情况),代码实现、调试更复杂。
- 写密集场景不如红黑树合适,所以工程里 C++ map 等容器底层选用红黑树而不是 AVL。
2. AVL 树的实现
2.1 AVL 树的结构
cpp
using namespace std;
template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& data = T())
: _pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _data(data)
, _bf(0)
{
}
AVLTreeNode<T>* _pLeft;
AVLTreeNode<T>* _pRight;
AVLTreeNode<T>* _pParent;
T _data;
int _bf; // 节点的平衡因子
};
// 左加 右减
// AVL: 二叉搜索树 + 平衡因子的限制
template<class T>
class AVLTree
{
typedef AVLTreeNode<T> Node;
public:
// 初始默认构造
AVLTree()
: _pRoot(nullptr)
{
}
private:
Node* _pRoot;
};
2.2 AVL 树的插入
2.2.1 AVL 插入的大概过程
向 AVL 树插入节点时,首先按照普通二叉搜索树规则找到空位插入新节点。插入完成后,从新节点向上回溯,依次更新沿途每个祖先节点的高度,计算平衡因子。
若所有节点平衡因子绝对值都不大于 1,树保持平衡,插入结束。
如果某个节点平衡因子超出范围,说明发生失衡,需要判断失衡类型:LL、RR、LR、RL,对应执行单旋转或者双旋转调整结构,让该子树恢复平衡。旋转完成后,继续向上检查上层节点是否失衡,直到根节点。AVL 依靠旋转保证任意节点左右子树高度差不超过 1,维持树高为对数级别,保证查找效率稳定。
2.2.2 平衡因子的更新
平衡因子 = 左子树高度 − 右子树高度。 插入新节点后,从新节点的父节点开始往根方向回溯:先更新当前节点的高度,再重新计算它的平衡因子。
- 平衡因子绝对值≤1:该节点平衡,继续向上检查祖先;
- 平衡因子绝对值>1:节点失衡,进行旋转调整;旋转完成后,更新旋转涉及节点的高度与平衡因子,再继续向上判断上层节点。 删除节点时变化更大,删除可能让祖先高度持续变化,需要一路向上更新高度、重算平衡因子,多次触发调整。
cpp
size_t _Height(Node* pRoot)
{
if (pRoot == nullptr)
{
return 0;
}
// max 找出两数最大数值
size_t left = _Height(pRoot->_pLeft);
size_t right = _Height(pRoot->_pRight);
return max(left,right) + 1;
}
// Bf 调整
void Bf(Node* root)
{
if (root == nullptr)
return;
int left = root->_pLeft ? _Height(root->_pLeft) : 0;
int right = root->_pRight ? _Height(root->_pRight) : 0;
root->_bf = left - right;
}
2.2.2 插入节点的更新平衡音系的代码实现
cpp
// 在AVL树中插入值为data的节点
bool Insert(const T& data)
{
if (_pRoot == nullptr)
{
_pRoot = new Node(data);
return true;
}
else
{
Node* dest = _pRoot, *cur = nullptr;
while (dest)
{
if (data < dest->_data)
{
cur = dest;
dest = dest->_pLeft;
}
else if (dest->_data < data)
{
cur = dest;
dest = dest->_pRight;
}
else
{
return false;
}
}
dest = new Node(data);
if (data < cur->_data)
{
cur->_pLeft = dest;
}
else if (cur->_data < data)
{
cur->_pRight = dest;
}
dest->_pParent = cur;
// 向上调整平衡数
Bf(cur);
while (cur != nullptr && abs(cur->_bf) < 2)
{
cur = cur->_pParent;
Bf(cur);
}
// 如果没有问题直接退出有平衡失衡的情况就进行旋转
if (cur == nullptr)
return true;
else if (cur->_bf == 2)
{
if (cur->_pLeft->_bf == 1)
{
RotateR(cur);
}
else if(cur->_pLeft->_bf == -1)
{
RotateLR(cur);
}
}
else if (cur->_bf == -2)
{
if (cur->_pRight->_bf == 1)
{
RotateRL(cur);
}
else if (cur->_pRight->_bf == -1)
{
RotateL(cur);
}
}
else
{
assert(false);
}
return true;
}
}
2.3 旋转
2.3.1 旋转的原则
旋转核心原则:不破坏二叉搜索树有序性,同时修正失衡,调整子树高度、更新平衡因子。
- 保持 BST 性质:旋转后依旧满足左子树值 < 根 < 右子树值,中序遍历结果不变。
- 转移子树:旋转时把中间那棵子树(被挤出来的子树)接到合适位置,避免丢失节点。
- 优先处理最深失衡节点:从下往上找到第一个失衡节点,以它为旋转中心调整。
- 旋转完成后,及时更新被改动节点的高度和平衡因子,再继续向上校验。
- 旋转只改变指针关系,不改变节点相对大小;单旋处理 LL、RR,双旋处理 LR、RL。
2.3.2 右单旋

cpp
// 考虑问题:
// 1,是否有 Chlid 的 左右节点是否为空(但其实右旋时就R 可能就是为空出现的可能比较小,出现的话就已经被判断需要双旋了)
// 2,考虑是否有 GrandParent 有需要进行两线对接,没有就需要进行提上来的 Child 的 Parent 为空
// 右单旋
void RotateR(Node* pParent)
{ // G G
// P C
// C -> L P
// L R R
//
// pParent -> P
//
// 最有可能发生_pRoot 位置就是 P 和 G ,其中 G 改变只是其中个别位置,对_pRoot 没有影响下
// 其中要关注就是 _pRoot 的 P 位置 和 C 位置
Node* Child = pParent->_pLeft;// C
Node* Parent = pParent;// P
Node* GrandParent = pParent->_pParent;// G
// 4 段代码维护
// P->C = P->R
Parent->_pLeft = Child->_pRight;
// R->P = R->P
if (Child->_pRight != nullptr)
{
Child->_pRight->_pParent = Parent;
}
// C->R = C->P
Child->_pRight = Parent;
// P->G = P->C
Parent->_pParent = Child;
// G->P = G->C
if (GrandParent != nullptr)
{
GrandParent->_pLeft == Parent ? GrandParent->_pLeft = Child : GrandParent->_pRight = Child;
// C->P = C->G
Child->_pParent = GrandParent;
}
else
{
// C->P = C->nullptr
Child->_pParent = nullptr;
_pRoot = Child;
}
// 调整平衡
Bf(Parent);
Bf(Child);
}
2.3.3 左单旋

cpp
// 左单旋
void RotateL(Node* pParent)
{ // G G
// P C
// C -> P R
// L R L
// pParent -> P
// 最有可能发生_pRoot 位置就是 P 和 G ,其中 G 改变只是其中个别位置,对_pRoot 没有影响下
// 其中要关注就是 _pRoot 的 P 位置 和 C 位置
// 4 段代码维护
// P->C = P->L
Node* Child = pParent->_pRight;// C
Node* Parent = pParent;// P
Node* GrandParent = pParent->_pParent;// G
// 4 段代码维护
// P->C = P->L
Parent->_pRight = Child->_pLeft;
// L->C = L->P
if (Child->_pLeft != nullptr)
{
Child->_pLeft->_pParent = Parent;
}
// C->L = C->P
Child->_pLeft = Parent;
// P->G = P->C
Parent->_pParent = Child;
if (GrandParent != nullptr)
{
// G->P = G->C
GrandParent->_pLeft == Parent ? GrandParent->_pLeft = Child : GrandParent->_pRight = Child;
// C->P = C->G
Child->_pParent = GrandParent;
}
else
{
// C->P = C->nullptr
Child->_pParent = nullptr;
_pRoot = Child;
}
// 调整平衡
Bf(Parent);
Bf(Child);
}
2.3.4 左右双旋
在部分情况种单旋已经没有办法满足了所有需要双旋来满足,第一次旋转是为了结构变成,符合左右单旋的结构。其次双旋可以复用前面的代码,以便于理解也可以不用写太多代码。

cpp
// 左右双旋
void RotateLR(Node* pParent)
{
/*
a a c
b -> c -> b a
c b
b->pParent
优先 c 进行 左旋
因为 c 进行 右旋
*/
RotateL(pParent->_pLeft);
RotateR(pParent);
}
2.3.5 右左双旋 
cpp
// 右左双旋
void RotateRL(Node* pParent)
{// pParent -> a
/*
a a c
b -> c -> b a
c b
b->pParent
优先 c 进行 右旋
因为 c 进行 左旋
*/
RotateR(pParent->_pRight);
RotateL(pParent);
}
2.4 AVL 树的查找
AVL 树的查找过程和普通二叉搜索树查找逻辑一致。从根节点出发,拿目标值和当前节点比较:目标更小就去往左子树查找,目标更大就去往右子树查找,相等则查找成功。如果走到空节点,代表查找失败。 区别在于 AVL 树受平衡约束限制,树高稳定为 logn,查找最坏时间复杂度是 O (log n),不会像普通 BST 那样退化成 O (n)。查找过程不会修改树结构、不改动高度和平衡因子,也不会触发旋转。
2.5 AVL 树的平衡检查
cpp
// AVL树的验证
bool IsAVLTree()
{
return _IsAVLTree(_pRoot);
}
private:
// 根据AVL树的概念验证pRoot是否为有效的AVL树
bool _IsAVLTree(Node* pRoot)
{
if (pRoot == nullptr)
{
return true;
}
bool put = _IsAVLTree(pRoot->_pLeft);
if (put == false || abs(pRoot->_bf) >= 2)
{
cout << "节点平衡异常" << endl;
return false;
}
put = _IsAVLTree(pRoot->_pRight);
return put;
}
2.6 AVL树的删除

删除的原理其实与插入的原理一致,由二叉搜索树的删除原理,在加上对应位置旋转操作即可。
AVLTree · 浪子·悠仁/C++ 知识库 - 码云 - 开源中国
