在数据结构的世界里,二叉搜索树(BST)是一种高效的动态查找结构,其插入、删除和查找操作的时间复杂度均为 O (h)(h 为树的高度)。然而,普通二叉搜索树存在一个致命缺陷:当插入序列有序或接近有序时,树会退化为单链表,此时操作时间复杂度骤降至 O (n),完全失去了高效性。 为解决这一问题,自平衡二叉搜索树应运而生。AVL 树作为最早发明的自平衡二叉搜索树,由前苏联科学家 G. M. Adelson-Velsky 和 E. M. Landis 于 1962 年在论文《An algorithm for the organization of information》中提出。它通过严格控制左右子树的高度差,确保树始终保持平衡状态,从而将操作时间复杂度稳定在 O (log n) 级别。 本文将从 AVL 树的基本概念出发,详细讲解其结构设计、插入逻辑、平衡调整、查找实现及平衡检测方法,并提供完整的代码实现,帮助大家深入理解并掌握这一经典数据结构。下面就让我们正式开始吧!
AVL 树是一种特殊的二叉搜索树,满足以下两个核心条件:
为了方便判断节点是否平衡,引入平衡因子的概念:
平衡因子就像 “风向标”,直观反映了节点左右子树的高度关系,是后续我们进行平衡调整操作的核心判断依据。


有人可能会有这样的疑问:既然要平衡,为什么不要求左右子树高度差为 0?这样不就完全平衡了吗?实际上,这是由树的节点数量决定的 —— 某些情况下,完全平衡是无法实现的。
例如:
因此,AVL 树选择 “高度差绝对值不超过 1” 作为平衡标准,既保证了树的高效性,又避免了无法实现的极端要求。
AVL 树的高度严格控制在 O (log n) 级别(n 为节点总数),这是因为其平衡性质限制了树的 “生长”:
因此,AVL 树的插入、删除、查找操作时间复杂度均为 O (log n),相比普通二叉搜索树有本质提升。
AVL 树的节点需要存储键值对、左右孩子指针、父节点指针(用于后续平衡因子更新)以及平衡因子。下面通过 C++ 模板来实现一下 AVL 树的节点结构和树结构。
template<class K, class V>
struct AVLTreeNode {
// 存储键值对(适用于字典场景,如key-value映射)
pair<K, V> _kv;
// 左右孩子指针
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
// 父节点指针(关键:用于插入后回溯更新平衡因子)
AVLTreeNode<K, V>* _parent;
// 平衡因子(初始为0,新节点无孩子)
int _bf;
// 构造函数:初始化节点
AVLTreeNode(const pair<K, V>& kv)
: _kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0) {}
};说明:
_parent是 AVL 树的关键设计之一:插入新节点后,需要从新节点向上回溯,更新所有祖先节点的平衡因子,父节点指针确保了回溯的可行性;_bf初始为 0:新节点无左、右孩子,左右子树高度差为 0。template<class K, class V>
class AVLTree {
// 类型别名:简化节点指针的使用
typedef AVLTreeNode<K, V> Node;
public:
// 构造函数:默认初始化根节点为nullptr(空树)
AVLTree() : _root(nullptr) {}
// 后续实现:插入、查找、平衡检测等成员函数
bool Insert(const pair<K, V>& kv);
Node* Find(const K& key);
bool IsBalanceTree();
// 中序遍历(用于验证二叉搜索树性质)
void InOrder() { _InOrder(_root); cout << endl; }
private:
// 根节点(私有成员,通过公有接口访问)
Node* _root;
// 辅助函数:递归中序遍历
void _InOrder(Node* root) {
if (root == nullptr) return;
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
// 辅助函数:计算节点高度
int _Height(Node* root);
// 辅助函数:递归检测平衡
bool _IsBalanceTree(Node* root);
// 旋转操作(私有:仅内部平衡调整使用)
void RotateR(Node* parent); // 右单旋
void RotateL(Node* parent); // 左单旋
void RotateLR(Node* parent); // 左右双旋
void RotateRL(Node* parent); // 右左双旋
};说明:
K和值类型V,通用性更强;_InOrder(中序遍历)、_Height(计算高度)、_IsBalanceTree(平衡检测)等,封装内部实现细节;RotateR、RotateL等为私有成员,仅在插入后平衡调整时调用,外部无需感知。AVL 树的插入是其核心操作,分为 “按二叉搜索树规则插入” 和 “回溯更新平衡因子并调整平衡” 两个阶段。
首先,按照普通二叉搜索树的插入逻辑找到插入位置,代码如下:
bool AVLTree<K, V>::Insert(const pair<K, V>& kv) {
// 情况1:树为空,直接创建根节点
if (_root == nullptr) {
_root = new Node(kv);
return true;
}
// 情况2:树非空,查找插入位置
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 {
// 键值已存在,插入失败(AVL树不允许重复键)
return false;
}
}
// 找到插入位置,创建新节点并建立父子关系
cur = new Node(kv);
if (parent->_kv.first < kv.first) {
// 新节点为父节点的右孩子
parent->_right = cur;
} else {
// 新节点为父节点的左孩子
parent->_left = cur;
}
// 建立父节点指针(关键:用于后续回溯)
cur->_parent = parent;
// 后续步骤:更新平衡因子并调整平衡(见3.3节)
// ...
}细节分析:
false;_parent指向其父节点,父节点的_left或_right指向新节点,确保回溯路径完整。插入新节点后,只有新节点的祖先节点的高度可能发生变化(子树高度变化会影响平衡因子)。因此,需要从父节点开始向上更新平衡因子,并判断是否需要停止或调整平衡。
_bf = 右子树高度 - 左子树高度;parent->_bf--;parent->_bf++;更新后父节点的平衡因子 | 含义(更新前后的变化) | 是否继续向上更新 | 原因 |
|---|---|---|---|
0 | 之前为 ±1,插入后变为 0 | 否 | 插入前父节点子树 “一高一低”,插入后 “等高”,子树高度不变,不影响上层节点 |
±1 | 之前为 0,插入后变为 ±1 | 是 | 插入前父节点子树 “等高”,插入后 “一高一低”,子树高度增加 1,影响上层节点 |
±2 | 之前为 ±1,插入后变为 ±2 | 否(需旋转) | 插入前父节点子树 “一高一低”,插入后 “更高的一侧更突出”,子树失衡,需旋转调整 |



// 接3.2节代码,继续在Insert函数中实现
// 步骤2:回溯更新平衡因子
while (parent) {
// 1. 更新当前父节点的平衡因子
if (cur == parent->_left) {
// 新节点是父节点的左孩子,父节点bf--
parent->_bf--;
} else {
// 新节点是父节点的右孩子,父节点bf++
parent->_bf++;
}
// 2. 判断是否需要继续更新或调整平衡
if (parent->_bf == 0) {
// 情况1:bf变为0,子树高度不变,停止更新
break;
} else if (parent->_bf == 1 || parent->_bf == -1) {
// 情况2:bf变为±1,子树高度增加1,继续向上更新
cur = parent;
parent = parent->_parent;
} else if (parent->_bf == 2 || parent->_bf == -2) {
// 情况3:bf变为±2,子树失衡,需要旋转调整
// 根据失衡类型选择对应的旋转操作
if (parent->_bf == 2) {
// 右子树过高,需左单旋或右左双旋
if (cur->_bf == 1) {
// 右子树的右子树过高(RR型),左单旋
RotateL(parent);
} else {
// 右子树的左子树过高(RL型),右左双旋
RotateRL(parent);
}
} else { // parent->_bf == -2
// 左子树过高,需右单旋或左右双旋
if (cur->_bf == -1) {
// 左子树的左子树过高(LL型),右单旋
RotateR(parent);
} else {
// 左子树的右子树过高(LR型),左右双旋
RotateLR(parent);
}
}
// 旋转后子树高度恢复,无需继续向上更新,插入结束
break;
} else {
// 异常情况:bf绝对值超过2(代码逻辑错误)
assert(false);
}
}
return true;代码说明如下:
parent != nullptr(直到根节点);parent->_bf == ±2时,根据cur->_bf(失衡子树的子节点平衡因子)判断失衡类型(LL、RR、LR、RL),选择对应的旋转操作;parent->_bf为其他值(如 3、-3),说明代码逻辑错误,通过assert断言提示。当某节点的平衡因子变为 ±2 时,子树失衡,需要通过旋转操作调整平衡。旋转的核心目标有两个:
根据失衡子树的结构,旋转分为四种类型:右单旋(LL 型)、左单旋(RR 型)、左右双旋(LR 型)、右左双旋(RL 型)。
LL 型失衡:失衡节点(设为parent)的平衡因子为 - 2(左子树过高),且其左孩子(设为subL)的平衡因子为 - 1(左子树的左子树过高)。
例如:
parent (bf=-2)
/
subL (bf=-1)
/
newNode 将subL提升为新的子树根节点,parent变为subL的右孩子,subL的右子树(设为subLR)变为parent的左子树。具体步骤如下:
subL的右子树subLR;subLR作为parent的左子树(若subLR非空,更新其_parent为parent);parent作为subL的右子树,更新parent的_parent为subL;subL与上层节点的关系(若parent是原根节点,则subL成为新根;否则,subL替换parent在其上层父节点中的位置);parent和subL的平衡因子为 0(旋转后子树平衡)。void AVLTree<K, V>::RotateR(Node* parent) {
// 1. 保存关键节点
Node* subL = parent->_left; // parent的左孩子(即将成为新根)
Node* subLR = subL->_right; // subL的右子树(即将成为parent的左子树)
Node* parentParent = parent->_parent; // parent的父节点(上层节点)
// 2. 调整subLR与parent的关系
parent->_left = subLR;
if (subLR != nullptr) {
subLR->_parent = parent;
}
// 3. 调整parent与subL的关系
subL->_right = parent;
parent->_parent = subL;
// 4. 调整subL与上层节点的关系
if (parentParent == nullptr) {
// 情况1:parent是原根节点,subL成为新根
_root = subL;
subL->_parent = nullptr;
} else {
// 情况2:parent是上层节点的左/右孩子,subL替换其位置
if (parentParent->_left == parent) {
parentParent->_left = subL;
} else {
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
// 5. 重置平衡因子(旋转后subL和parent均平衡)
parent->_bf = 0;
subL->_bf = 0;
}示例图解如下:





RR 型失衡:失衡节点(parent)的平衡因子为 2(右子树过高),且其右孩子(subR)的平衡因子为 1(右子树的右子树过高)。
例如:
parent (bf=2)
\
subR (bf=1)
\
newNode 与右单旋对称:将subR提升为新的子树根节点,parent变为subR的左孩子,subR的左子树(subRL)变为parent的右子树。
void AVLTree<K, V>::RotateL(Node* parent) {
// 1. 保存关键节点
Node* subR = parent->_right; // parent的右孩子(即将成为新根)
Node* subRL = subR->_left; // subR的左子树(即将成为parent的右子树)
Node* parentParent = parent->_parent; // parent的父节点
// 2. 调整subRL与parent的关系
parent->_right = subRL;
if (subRL != nullptr) {
subRL->_parent = parent;
}
// 3. 调整parent与subR的关系
subR->_left = parent;
parent->_parent = subR;
// 4. 调整subR与上层节点的关系
if (parentParent == nullptr) {
// 情况1:parent是原根节点,subR成为新根
_root = subR;
subR->_parent = nullptr;
} else {
// 情况2:parent是上层节点的左/右孩子,subR替换其位置
if (parentParent->_left == parent) {
parentParent->_left = subR;
} else {
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
// 5. 重置平衡因子
parent->_bf = 0;
subR->_bf = 0;
}示例图解如下:

LR 型失衡:失衡节点(parent)的平衡因子为 - 2(左子树过高),且其左孩子(subL)的平衡因子为 1(左子树的右子树过高)。
例如:
parent (bf=-2)
/
subL (bf=1)
\
subLR (newNode)左右双旋是 “左单旋 + 右单旋” 的组合:
subL为旋转点进行左单旋,将subLR提升为subL的父节点(此时失衡类型转为 LL 型);parent为旋转点进行右单旋,将subLR提升为新的子树根节点,恢复平衡。 旋转后平衡因子的重置需根据subLR(双旋的核心节点)插入前的平衡因子判断,分为三种场景:
subLR->_bf == 0:插入前subLR无孩子,旋转后parent、subL、subLR的平衡因子均为 0;subLR->_bf == -1:插入前subLR的左子树有节点,旋转后parent->_bf = 1,subL->_bf = 0,subLR->_bf = 0;subLR->_bf == 1:插入前subLR的右子树有节点,旋转后parent->_bf = 0,subL->_bf = -1,subLR->_bf = 0。void AVLTree<K, V>::RotateLR(Node* parent) {
// 1. 保存关键节点
Node* subL = parent->_left;
Node* subLR = subL->_right;
// 记录subLR的平衡因子(用于后续重置)
int bf = subLR->_bf;
// 2. 第一步:以subL为旋转点进行左单旋(转为LL型)
RotateL(subL);
// 3. 第二步:以parent为旋转点进行右单旋(恢复平衡)
RotateR(parent);
// 4. 根据subLR的原始bf重置平衡因子
if (bf == 0) {
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 0;
} else if (bf == -1) {
// subLR的左子树插入节点,parent的右子树高度增加
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 1;
} else if (bf == 1) {
// subLR的右子树插入节点,subL的左子树高度增加
subL->_bf = -1;
subLR->_bf = 0;
parent->_bf = 0;
} else {
assert(false); // 异常情况
}
}示例图解如下:



RL 型失衡:失衡节点(parent)的平衡因子为 2(右子树过高),且其右孩子(subR)的平衡因子为 - 1(右子树的左子树过高)。
例如:
parent (bf=2)
\
subR (bf=-1)
/
subRL (newNode)与左右双旋对称,是 “右单旋 + 左单旋” 的组合:
subR为旋转点进行右单旋,将subRL提升为subR的父节点(此时失衡类型转为 RR 型);parent为旋转点进行左单旋,将subRL提升为新的子树根节点,恢复平衡。 与左右双旋类似,根据subRL的原始平衡因子判断:
subRL->_bf == 0:旋转后parent、subR、subRL的平衡因子均为 0;subRL->_bf == 1:旋转后parent->_bf = -1,subR->_bf = 0,subRL->_bf = 0;subRL->_bf == -1:旋转后parent->_bf = 0,subR->_bf = 1,subRL->_bf = 0。void AVLTree<K, V>::RotateRL(Node* parent) {
// 1. 保存关键节点
Node* subR = parent->_right;
Node* subRL = subR->_left;
// 记录subRL的平衡因子
int bf = subRL->_bf;
// 2. 第一步:以subR为旋转点进行右单旋(转为RR型)
RotateR(subR);
// 3. 第二步:以parent为旋转点进行左单旋(恢复平衡)
RotateL(parent);
// 4. 根据subRL的原始bf重置平衡因子
if (bf == 0) {
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
} else if (bf == 1) {
// subRL的右子树插入节点,parent的左子树高度增加
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
} else if (bf == -1) {
// subRL的左子树插入节点,subR的右子树高度增加
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
} else {
assert(false); // 异常情况
}
}示例图解如下:

AVL 树的查找逻辑与普通二叉搜索树完全一致,因为平衡调整不会破坏二叉搜索树的性质。由于 AVL 树高度为 O (log n),查找时间复杂度也为 O (log n)。
template<class K, class V>
typename AVLTree<K, V>::Node* AVLTree<K, V>::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;
}
}
// 遍历结束未找到,返回nullptr
return nullptr;
}代码说明:
typename关键字:因为Node是模板类AVLTree<K,V>的嵌套类型,编译器需要明确其为类型(而非成员变量); 以 AVL 树{5,3,7,2,4,6,8}为例,查找键值为 6 的节点:
为了验证实现的 AVL 树是否正确,需要通过代码检测其平衡性质。平衡检测包括两个核心:
_bf等于 “右子树高度 - 左子树高度”(确保平衡因子更新正确)。 首先实现_Height函数,递归计算节点的高度(空节点高度为 0,非空节点高度为左右子树最大高度 + 1):
template<class K, class V>
int AVLTree<K, V>::_Height(Node* root) {
if (root == nullptr) {
return 0;
}
// 递归计算左、右子树高度
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
// 当前节点高度 = 左右子树最大高度 + 1
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
} 实现_IsBalanceTree函数,递归检测每个节点的平衡性质:
template<class K, class V>
bool AVLTree<K, V>::_IsBalanceTree(Node* root) {
// 空树是AVL树
if (root == nullptr) {
return true;
}
// 1. 计算当前节点的实际平衡因子(右高 - 左高)
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
int actualBf = rightHeight - leftHeight;
// 2. 检测平衡因子是否异常
// 情况1:实际平衡因子绝对值超过1(失衡)
if (abs(actualBf) >= 2) {
cout << "节点" << root->_kv.first << "高度差异常:实际bf=" << actualBf << endl;
return false;
}
// 情况2:存储的bf与实际bf不相等(平衡因子更新错误)
if (root->_bf != actualBf) {
cout << "节点" << root->_kv.first << "平衡因子异常:存储bf=" << root->_bf
<< ",实际bf=" << actualBf << endl;
return false;
}
// 3. 递归检测左、右子树
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
// 公有接口:对外提供平衡检测
template<class K, class V>
bool AVLTree<K, V>::IsBalanceTree() {
return _IsBalanceTree(_root);
}通过插入测试用例,验证 AVL 树的平衡性质:
// 测试用例1:常规场景(包含双旋)
void TestAVLTree1() {
AVLTree<int, int> t;
// 插入序列:包含LR和RL双旋场景
int a[] = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};
for (auto e : a) {
t.Insert({e, e});
}
// 验证二叉搜索树性质:中序遍历应为升序
cout << "中序遍历结果:";
t.InOrder(); // 预期:1 2 3 4 5 6 7 14 15 16
// 验证平衡性质
if (t.IsBalanceTree()) {
cout << "TestAVLTree1:AVL树平衡检测通过!" << endl;
} else {
cout << "TestAVLTree1:AVL树平衡检测失败!" << endl;
}
}
// 测试用例2:大量随机值插入(验证性能与稳定性)
void TestAVLTree2() {
const int N = 100000; // 插入10万个随机值
vector<int> v;
v.reserve(N); // 预分配空间,避免扩容开销
// 生成10万个不重复的随机值
srand(time(0));
for (size_t i = 0; i < N; i++) {
v.push_back(rand() + i); // 加i避免重复
}
// 插入性能测试
size_t insertBegin = clock();
AVLTree<int, int> t;
for (auto e : v) {
t.Insert({e, e});
}
size_t insertEnd = clock();
cout << "TestAVLTree2:插入" << N << "个节点耗时:" << (insertEnd - insertBegin) << "ms" << endl;
// 平衡检测
if (t.IsBalanceTree()) {
cout << "TestAVLTree2:AVL树平衡检测通过!" << endl;
} else {
cout << "TestAVLTree2:AVL树平衡检测失败!" << endl;
}
// 树高度检测(应接近log2(N) ≈ 17)
int height = t._Height(t._root);
cout << "TestAVLTree2:AVL树高度为:" << height << endl;
// 查找性能测试(查找随机值)
size_t findBegin = clock();
for (size_t i = 0; i < N; i++) {
int key = rand() + i;
t.Find(key);
}
size_t findEnd = clock();
cout << "TestAVLTree2:查找" << N << "个随机值耗时:" << (findEnd - findBegin) << "ms" << endl;
}
// 主函数:运行测试用例
int main() {
TestAVLTree1();
cout << "------------------------" << endl;
TestAVLTree2();
return 0;
}本文在这里没有详细实现 AVL 树的删除操作,因为其逻辑是比插入更复杂的:
若需深入学习,大家可以参考《殷人昆 数据结构:用面向对象方法与 C++ 语言描述》等经典教材。
AVL 树作为经典的自平衡二叉搜索树,通过严格的平衡控制(左右子树高度差≤1)和高效的旋转操作,确保了插入、删除、查找的时间复杂度稳定在 O (log n) 级别,解决了普通二叉搜索树的退化问题。 AVL 树的实现难度主要在于旋转操作的逻辑和平衡因子的更新,建议读者结合代码手动模拟插入和旋转过程,加深理解。在实际工程中,AVL 树适用于对查找性能要求高、插入删除频率适中的场景(若插入删除频率极高,可考虑红黑树,其旋转次数更少)。 希望本文能帮助大家全面掌握 AVL 树的原理与实现,为后续学习更复杂的数据结构(如红黑树)打下坚实的基础!