AVL 树是最早发明的自平衡二叉搜索树(Self-Balancing Binary Search Tree),其核心目标是解决普通二叉搜索树(BST)在有序插入时退化为单支树(时间复杂度 O (N))的缺陷。通过自动调整树的结构,AVL 树保证任意节点的左右子树高度差不超过 1,从而将增删查操作的时间复杂度稳定在 O (logN)。本文将从 “概念→结构→核心操作” 的路径,系统讲解 AVL 树的原理与实现,帮你彻底掌握这一经典数据结构。
一、AVL 树的概念:什么是自平衡二叉搜索树?
1.1 AVL 树的定义
AVL 树本质是一棵满足以下条件的二叉搜索树:
- 左右子树均为 AVL 树(递归定义);
- 任意节点的左右子树高度差的绝对值 ≤ 1(平衡条件)。
为了直观判断平衡状态,AVL 树引入平衡因子(Balance Factor, BF) 的概念:平衡因子 = 右子树高度 - 左子树高度根据 AVL 树的平衡条件,任意节点的平衡因子只能是 -1、0、1。
1.2 为什么不要求高度差为 0?
理想的 “完全平衡”(高度差为 0)虽能达到最优性能,但在很多场景下无法实现。例如:
2 个节点的 AVL 树:根节点左子树为空,右子树高度 1,高度差 1(无法做到 0);
4 个节点的 AVL 树:根节点左子树高度 1,右子树高度 2,高度差 1(无法做到 0)。
因此,AVL 树选择 “高度差≤1” 作为平衡条件,既保证了树的高度接近完全二叉树(高度为 log₂N),又避免了过度追求平衡导致的复杂操作。
1.3 AVL 树的核心价值
普通 BST 在最坏情况下(有序插入)会退化为单支树,增删查时间复杂度退化为 O (N);而 AVL 树通过自平衡机制,确保树的高度始终为 O (logN),从而将所有操作的时间复杂度稳定在 O (logN),适用于对效率要求较高的场景(如数据库索引、高频检索系统)。
二、AVL 树的结构设计
AVL 树的节点需要存储以下信息:
键值对(Key-Value,支持映射场景);
左 / 右子节点指针;
父节点指针(用于插入后更新平衡因子和旋转操作);
平衡因子(BF)。
2.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>& kv)
: _kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0) {}
};
2.2 AVL 树类框架
template <class K, class V>
class AVLTree {
typedef AVLTreeNode<K, V> Node;
public:
// 核心接口:插入、查找、平衡检测
bool Insert(const pair<K, V>& kv);
Node* Find(const K& key);
bool IsBalanceTree(); // 验证AVL树是否平衡
private:
// 辅助接口:旋转、高度计算、平衡检测递归实现
void RotateR(Node* parent); // 右单旋
void RotateL(Node* parent); // 左单旋
void RotateLR(Node* parent); // 左右双旋
void RotateRL(Node* parent); // 右左双旋
int _Height(Node* root); // 计算子树高度
bool _IsBalanceTree(Node* root); // 递归检测平衡
private:
Node* _root = nullptr; // 根节点
};
三、AVL 树的核心操作:插入与旋转
AVL 树的插入流程分为两步:
- 按二叉搜索树规则插入新节点;
- 回溯更新平衡因子,若出现不平衡(BF=±2),通过旋转调整树结构,恢复平衡。
3.1 插入流程详解
步骤 1:按 BST 规则插入新节点
与普通 BST 插入逻辑一致:从根节点开始比较,小于当前节点则向左,大于则向右,找到空位后创建新节点,并链接到父节点。
步骤 2:回溯更新平衡因子
插入新节点后,仅会影响 “新节点→根节点” 路径上的祖先节点的平衡因子(子树高度变化)。更新规则如下:
若新节点在父节点的左子树:父节点的 BF -= 1;
若新节点在父节点的右子树:父节点的 BF += 1;
更新后根据父节点的 BF 值决定后续操作:
| 父节点 BF 值 | 含义 | 后续操作 |
|---|---|---|
| 0 | 插入前父节点 BF 为 ±1,插入后子树高度不变 | 停止更新(不影响上层节点) |
| ±1 | 插入前父节点 BF 为 0,插入后子树高度 + 1 | 继续向上更新(影响上层节点) |
| ±2 | 插入前父节点 BF 为 ±1,插入后子树高度差超限 | 触发旋转(恢复平衡后停止更新) |
| 其他值 | 逻辑错误(如 BF=3) | 断言报错 |
步骤 3:插入与平衡因子更新代码实现
bool Insert(const pair<K, V>& kv) {
// 1. 空树:直接创建根节点
if (_root == nullptr) {
_root = new Node(kv);
return true;
}
// 2. 按BST规则找到插入位置
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 {
// Key已存在,插入失败
return false;
}
}
// 3. 创建新节点并链接到父节点
cur = new Node(kv);
if (parent->_kv.first < kv.first) {
parent->_right = cur;
} else {
parent->_left = cur;
}
cur->_parent = parent;
// 4. 回溯更新平衡因子
while (parent) {
// 4.1 更新当前父节点的BF
if (cur == parent->_left) {
parent->_bf--;
} else {
parent->_bf++;
}
// 4.2 根据BF值决定后续操作
if (parent->_bf == 0) {
// 子树高度不变,停止更新
break;
} else if (parent->_bf == 1 || parent->_bf == -1) {
// 子树高度+1,继续向上更新
cur = parent;
parent = parent->_parent;
} else if (parent->_bf == 2 || parent->_bf == -2) {
// 不平衡,触发旋转
if (parent->_bf == 2) {
// 右子树过高:左单旋或右左双旋
if (cur->_bf == 1) {
RotateL(parent);
} else { // cur->_bf == -1
RotateRL(parent);
}
} else { // parent->_bf == -2
// 左子树过高:右单旋或左右双旋
if (cur->_bf == -1) {
RotateR(parent);
} else { // cur->_bf == 1
RotateLR(parent);
}
}
// 旋转后子树高度恢复,停止更新
break;
} else {
// 异常BF值(如3),断言报错
assert(false);
}
}
return true;
}
3.2 旋转操作:恢复平衡的核心
当某节点的 BF 为 ±2 时,需通过旋转调整树结构,目标是:
- 恢复 AVL 树的平衡(BF∈{-1,0,1});
- 降低该子树的高度(恢复到插入前的高度,避免影响上层节点)。
根据不平衡的类型,旋转分为四种:右单旋、左单旋、左右双旋、右左双旋。
3.2.1 右单旋(处理左子树过高)
适用场景:父节点 BF=-2,且左子节点 BF=-1(左子树的左子树过高)。
旋转原理(抽象模型):
假设有节点parent(BF=-2),左子节点subL(BF=-1),subL的右子树subLR(高度 h):
- 将
subLR作为parent的左子树(若subLR非空,更新其 parent 指针); - 将
parent作为subL的右子树(更新parent的 parent 指针); - 将
subL作为新的根节点,链接到原parent的父节点; - 重置
parent和subL的 BF 为 0(旋转后子树高度恢复)。
右单旋代码实现:
void RotateR(Node* parent) {
Node* subL = parent->_left; // 左子节点
Node* subLR = subL->_right; // 左子节点的右子树
Node* parentParent = parent->_parent; // 原父节点的父节点
// 步骤1:subLR链接到parent的左子树
parent->_left = subLR;
if (subLR) {
subLR->_parent = parent;
}
// 步骤2:parent链接到subL的右子树
subL->_right = parent;
parent->_parent = subL;
// 步骤3:subL链接到原parent的父节点
if (parentParent == nullptr) {
// 原parent是根节点,更新_root
_root = subL;
subL->_parent = nullptr;
} else {
// 原parent是子树,链接到上层
if (parent == parentParent->_left) {
parentParent->_left = subL;
} else {
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
// 步骤4:重置平衡因子
parent->_bf = 0;
subL->_bf = 0;
}
3.2.2 左单旋(处理右子树过高)
适用场景:父节点 BF=2,且右子节点 BF=1(右子树的右子树过高)。
旋转原理(抽象模型):
与右单旋对称:
- 将右子节点
subR的左子树subRL作为parent的右子树; - 将
parent作为subR的左子树; - 将
subR作为新的根节点,链接到原parent的父节点; - 重置
parent和subR的 BF 为 0。
左单旋代码实现:
void RotateL(Node* parent) {
Node* subR = parent->_right; // 右子节点
Node* subRL = subR->_left; // 右子节点的左子树
Node* parentParent = parent->_parent; // 原父节点的父节点
// 步骤1:subRL链接到parent的右子树
parent->_right = subRL;
if (subRL) {
subRL->_parent = parent;
}
// 步骤2:parent链接到subR的左子树
subR->_left = parent;
parent->_parent = subR;
// 步骤3:subR链接到原parent的父节点
if (parentParent == nullptr) {
_root = subR;
subR->_parent = nullptr;
} else {
if (parent == parentParent->_left) {
parentParent->_left = subR;
} else {
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
// 步骤4:重置平衡因子
parent->_bf = 0;
subR->_bf = 0;
}
3.2.3 左右双旋(处理左子树的右子树过高)
适用场景:父节点 BF=-2,且左子节点 BF=1(左子树的右子树过高)。
旋转原理:
左右双旋是 “左单旋 + 右单旋” 的组合:
- 对左子节点
subL执行左单旋(将subL的右子树subLR提升为新的subL); - 对原父节点
parent执行右单旋(恢复平衡); - 根据
subLR的原始 BF 值,调整parent、subL、subLR的 BF(分三种场景):
subLR原始 BF | 旋转后 BF 值 |
|---|---|
| 0 | parent->_bf=0,subL->_bf=0,subLR->_bf=0 |
| -1 | parent->_bf=1,subL->_bf=0,subLR->_bf=0 |
| 1 | parent->_bf=0,subL->_bf=-1,subLR->_bf=0 |
左右双旋代码实现:
void RotateLR(Node* parent) {
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf; // 记录subLR原始BF
// 步骤1:对subL执行左单旋
RotateL(subL);
// 步骤2:对parent执行右单旋
RotateR(parent);
// 步骤3:根据subLR原始BF调整平衡因子
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);
}
}
3.2.4 右左双旋(处理右子树的左子树过高)
适用场景:父节点 BF=2,且右子节点 BF=-1(右子树的左子树过高)。
旋转原理:
与左右双旋对称,是 “右单旋 + 左单旋” 的组合:
- 对右子节点
subR执行右单旋(将subR的左子树subRL提升为新的subR); - 对原父节点
parent执行左单旋(恢复平衡); - 根据
subRL的原始 BF 值,调整parent、subR、subRL的 BF。
右左双旋代码实现:
void RotateRL(Node* parent) {
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf; // 记录subRL原始BF
// 步骤1:对subR执行右单旋
RotateR(subR);
// 步骤2:对parent执行左单旋
RotateL(parent);
// 步骤3:根据subRL原始BF调整平衡因子
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);
}
}
3.3 查找操作
AVL 树的查找逻辑与普通 BST 完全一致 —— 按 Key 的大小定向遍历,时间复杂度 O (logN)。
代码实现:
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 {
// 找到Key,返回节点指针(可通过->second访问Value)
return cur;
}
}
// Key不存在
return nullptr;
}
四、AVL 树的平衡检测
为了验证 AVL 树的实现是否正确,需通过代码检测两点:
- 任意节点的左右子树高度差≤1;
- 任意节点的 BF 值与 “右高 - 左高” 的计算结果一致。
4.1 辅助函数:计算子树高度
int _Height(Node* root) {
if (root == nullptr) {
return 0;
}
// 递归计算左右子树高度,取最大值+1(当前节点高度)
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
return leftH > rightH ? leftH + 1 : rightH + 1;
}
4.2 平衡检测函数
bool _IsBalanceTree(Node* root) {
// 空树是AVL树
if (root == nullptr) {
return true;
}
// 计算当前节点的实际平衡因子(右高-左高)
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
int realBF = rightH - leftH;
// 检测:1. BF值是否匹配;2. 高度差是否≤1
if (root->_bf != realBF || abs(realBF) > 1) {
cout << "节点Key:" << root->_kv.first
<< ",记录BF:" << root->_bf
<< ",实际BF:" << realBF << "(不平衡)" << endl;
return false;
}
// 递归检测左右子树
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
// 对外接口
bool IsBalanceTree() {
return _IsBalanceTree(_root);
}
4.3 测试代码
void TestAVLTree() {
// 测试用例:包含双旋场景
int a[] = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};
AVLTree<int, int> t;
for (auto e : a) {
t.Insert({e, e});
}
// 中序遍历(验证BST特性:有序)
cout << "中序遍历(有序):";
t.InOrder(); // 需自行实现InOrder接口
cout << endl;
// 平衡检测
if (t.IsBalanceTree()) {
cout << "AVL树平衡检测通过!" << endl;
} else {
cout << "AVL树平衡检测失败!" << endl;
}
// 查找测试
Node* ret = t.Find(7);
if (ret) {
cout << "找到Key=7,Value=" << ret->_kv.second << endl;
} else {
cout << "未找到Key=7" << endl;
}
}
五、AVL 树的删除(扩展)
AVL 树的删除逻辑比插入更复杂,核心难点在于:
- 删除节点后,回溯更新平衡因子时,可能触发多轮旋转(插入仅需一轮);
- 若删除节点的子树高度降低,需继续向上更新,直到根节点或 BF 恢复为 ±1。
由于删除操作在实际应用中频率较低(且实现复杂),本文暂不展开,感兴趣的读者可参考《数据结构(殷人昆版)》或 STL 源码中 AVL 树的实现。
六、总结
AVL 树是平衡二叉搜索树的经典实现,其核心价值在于通过 “平衡因子 + 旋转” 机制,将树的高度稳定在 O (logN),确保增删查操作的时间复杂度为 O (logN)。关键要点总结如下:
- 平衡条件:任意节点的左右子树高度差≤1,BF∈{-1,0,1};
- 插入流程:BST 插入→回溯更新 BF→旋转恢复平衡(四种旋转场景);
- 旋转目标:恢复平衡的同时降低子树高度,避免影响上层节点;
- 适用场景:高频检索、低频率删除的场景(如内存数据库索引)。
1539

被折叠的 条评论
为什么被折叠?



