
🔥草莓熊Lotso:个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!
🎬 博主简介:

文章目录
- 前言:
- 一. AVL 树核心概念:什么是 “高度平衡”?
- 二. AVL 树结构设计:节点与类定义
- 三. AVL 树插入:从 BST 插入到平衡维护
-
- 3.1 插入完整代码(带详细注释)
- 3.2 平衡因子更新关键逻辑
- 四. AVL 树旋转:失衡恢复的四种方式
-
- 4.1 右单旋(RotateR):左子树过高(父 BF=-2,子 BF=-1)
- 4.2 左单旋(RotateL):右子树过高(父 BF=2,子 BF=1)
- 4.3 左右双旋(RotateLR):左子树的右子树过高(父 BF=-2,子 BF=1)
- 4.4 右左双旋(RotateRL):右子树的左子树过高(父 BF=2,子 BF=-1)
- 五. AVL树的查找和平衡检查:验证树的正确性
-
- 5.1 AVL树的查找:(与二叉搜索树一致,O(logN))
- 5.2 AVL 树验证:如何判断实现正确?
- 结尾:
前言:
在二叉搜索树(BST)中,若插入顺序有序(如递增或递减),树会退化为链表,导致增删查效率从
O(logN) 骤降至 O(N)。为解决这一问题,AVL 树(自平衡二叉搜索树)应运而生 —— 它通过控制左右子树的高度差(平衡因子),确保树始终保持 “高度平衡”,从而稳定维持O(logN)的高效操作。本文将从 AVL 树的核心概念切入,结合完整代码实现,详解插入逻辑、平衡因子更新与四种旋转操作,帮你彻底掌握这一经典数据结构。
一. AVL 树核心概念:什么是 “高度平衡”?
AVL 树是一种特殊的二叉搜索树,满足两个核心条件:
- 二叉搜索树特性:左子树所有节点值 < 根节点值 < 右子树所有节点值;
- 高度平衡特性:任意节点的左右子树高度差的绝对值 ≤ 1。
为了更直观判断平衡状态,引入了平衡因子(balance factor):平衡因子 = 右子树高度 - 左子树高度
AVL 树中,任意节点的平衡因子只能是 -1、0、1;若平衡因子为 2 或 -2,说明树已失衡,需通过 “旋转” 恢复平衡。

思考:为什么不要求平衡因子为 0?
某些场景下(如 2 个节点、4 个节点),树无法做到左右子树高度完全相等(如根节点左子树高 1,右子树高 0),此时平衡因子为 1,仍满足 “高度差≤1” 的平衡要求。
二. AVL 树结构设计:节点与类定义
AVL 树的节点需存储键值对、左右孩子指针、父指针(用于更新平衡因子)和平衡因子。类定义如下:
1#pragma once 2#include<iostream> 3#include<assert.h> 4using namespace std; 5 6// AVL树节点的结构 7template<class K, class V> 8struct AVLTreeNode 9{ 10 pair<K, V> _kv; // 存储键值对(Key-Value) 11 AVLTreeNode<K, V>* _parent; // 父节点指针(后续更新平衡因子要用到) 12 AVLTreeNode<K, V>* _left; 13 AVLTreeNode<K, V>* _right; 14 int _bf; // 平衡因子:右子树高度 - 左子树高度 15 16 AVLTreeNode(const pair<K, V>& kv) 17 : _kv(kv) 18 , _parent(nullptr) 19 , _left(nullptr) 20 , _right(nullptr) 21 , _bf(0) 22 {} 23}; 24 25// AVL树类 26template<class K, class V> 27class AVLTree 28{ 29 typedef AVLTreeNode<K, V> Node; 30public: 31 // 插入、旋转等核心接口(下文会详细解析) 32 bool Insert(const pair<K, V>& kv); 33 void RotateR(Node* parent); // 右单旋 34 void RotateL(Node* parent); // 左单旋 35 void RotateLR(Node* parent); // 左右双旋 36 void RotateRL(Node* parent); // 右左双旋 37 Node* Find(const K& key); // 查找(和二叉搜索树一样) 38 39private: 40 Node* _root = nullptr; 41}; 42
三. AVL 树插入:从 BST 插入到平衡维护
AVL 树的插入流程分为两步:按 BST 规则插入节点 → 回溯更新平衡因子,失衡则旋转恢复。这是 AVL 树维持平衡的核心环节,需重点理解平衡因子的更新逻辑与停止条件。
3.1 插入完整代码(带详细注释)
1public: 2bool Insert(const pair<K,V>& kv) 3{ 4 // 情况1:树为空,直接创建根节点 5 if (_root == nullptr) 6 { 7 _root = new Node(kv); 8 return true; 9 } 10 // 情况2:树非空,遍历找插入位置 11 Node* parent = nullptr;// 记录cur的父节点(用于后续链接新节点) 12 Node* cur = _root; 13 while (cur) 14 { 15 if (cur->_kv.first < kv.first) 16 { 17 parent = cur; 18 cur = cur->_right; // 比当前节点大,向右走 19 } 20 else if (cur->_kv.first > kv.first) 21 { 22 parent = cur; 23 cur = cur->_left;// 比当前节点小,向左走 24 25 } 26 else return false;// 值已存在,不支持插入,返回false 27 } 28 29 // 创建新节点,并链接到parent的左/右孩子 30 cur = new Node(kv); 31 if (parent->_kv.first < kv.first) 32 { 33 parent->_right = cur;// 插入值比parent大,作为右孩子 34 } 35 else { 36 parent->_left = cur; // 插入值比parent小,作为左孩子 37 } 38 39 cur->_parent = parent; 40 41 // 更新平衡因子(从新节点的父节点开始向上) 42 while (parent) 43 { 44 // 更新当前父节点的平衡因子 45 if (cur == parent->_left) 46 { 47 // 新节点在父节点的左子树 → 左子树高度+1 → 平衡因子-1 48 parent->_bf--; 49 } 50 else 51 { 52 // 新节点在父节点的右子树 → 右子树高度+1 → 平衡因子+1 53 parent->_bf++; 54 } 55 56 // 根据平衡因子判断是否继续更新或旋转 57 if (parent->_bf == 0) 58 { 59 // parent所在的子树高度不变,不会再影响上一层,更新结束 60 // 平衡因子变为0 → 父节点子树高度不变(插入前左 / 右高1,插入后平衡) 61 break; 62 } 63 else if (parent->_bf == 1 || parent->_bf == -1) 64 { 65 // parent 所在的子树高度变了,会再影响上一层,继续往上面更新 66 // 平衡因子变为1 / -1 → 父节点子树高度 + 1(插入前平衡,插入后单侧高1) 67 cur = parent; 68 parent = parent->_parent; 69 } 70 else if (parent->_bf == 2 || parent->_bf == -2) 71 { 72 // parent 所在的子树已经不平衡了,需要旋转处理 73 // 平衡因子变为2 / -1 → 父节点子树失衡,需旋转恢复平衡 74 if (parent->_bf == -2 && cur->_bf == -1) 75 { 76 // 父节点BF=-2(左子树高),当前节点BF=-1(左子树高)→ 右单旋 77 RotateR(parent); 78 } 79 else if (parent->_bf == 2 && cur->_bf == 1) 80 { 81 // 父节点BF=2(右子树高),当前节点BF=1(右子树高)→ 左单旋 82 RotateL(parent); 83 } 84 else if (parent->_bf == -2 && cur->_bf == 1) 85 { 86 // 父节点BF=-2(左子树高),当前节点BF=1(右子树高)→ 左右双旋 87 RotateLR(parent); 88 } 89 else 90 { 91 // 父节点BF=2(右子树高),当前节点BF=-1(左子树高)→ 右左双旋 92 RotateRL(parent); 93 } 94 // 旋转后,失衡子树高度恢复到插入前,不会影响上层,更新结束 95 break; 96 } 97 else 98 { 99 // 异常情况:平衡因子为其他值(如3、-3) 100 assert(false); 101 } 102 } 103 return true; 104} 105
3.2 平衡因子更新关键逻辑
更新平衡因子时,核心是判断 “当前子树高度是否变化”,进而决定是否继续向上更新:
BF=0:插入前父节点子树 “有一侧高 1”(如 BF=1 或 - 1),插入后两侧平衡,高度不变 → 停止更新;BF=1/-1:插入前父节点子树 “平衡”(BF=0),插入后 “有一侧高 1”,高度 + 1 → 继续更新;BF=2/-2:插入前父节点子树 “有一侧高 1”,插入后 “有一侧高 2”,失衡 → 旋转后停止更新。- 更新到根,根的平衡因子是1或-1也停止。
我们来看看下面几个情况图示:
更新到 10 结点,平衡因子为2,10所在的子树已经不平衡,需要旋转处理

更新到中间结点,3为根的子树高度不变,不会影响上一层,更新结束。

最坏情况:更新到根为止

四. AVL 树旋转:失衡恢复的四种方式
当节点平衡因子为 2 或 - 2 时,需通过 “旋转” 恢复平衡。旋转的核心目标有两个:维持二叉搜索树特性 + 降低失衡子树高度。根据失衡形态,分为四种旋转方式:
4.1 右单旋(RotateR):左子树过高(父 BF=-2,子 BF=-1)
- 下图中展现的是10为根的树,有a/b/c抽象为三颗高度为h的子树(h>=0),a/b/c均符合AVL树的要求。10可能是整颗树的根。这里a/b/c是高度为h的子树,是一种概括抽象的表示,他代表了所有右单旋的场景,但实际右单旋的场景很多,大家可以看看后面的实际场景图。

- 在 a 子树中插入一个新结点,导致a子树的高度从h变成h+1,不断向上更新平衡因子,导致10的平衡因子从-1变成-2,10为根的树左右高度差超过1,违反平衡规则。10为根的树的左边太高了,需要往右边旋转,控制两颗树的平衡。
旋转核心步骤:
- 记录父节点(
parent)的左子树(subL)和 subL 的右子树(subLR); - 将 subLR 作为 parent 的左子树(若 subLR 非空,更新其 parent 指针);
- 将 parent 作为 subL 的右子树,更新 parent 的 parent 指针(提前存一下祖父结点,再更新);
- 链接 subL 与祖父节点(若 parent 是根节点,更新_root);
- 重置 parent 和 subL 的平衡因子为 0。
实际场景图:
代码实现(注意看注释):
1public: 2void RotateR(Node* parent) 3{ 4 Node* subL = parent->_left; // parent的左子树(即将成为新根) 5 Node* subLR = subL->_right; // subL的右子树(需转移给parent) 6 7 // 步骤1:将subLR作为parent的左子树 8 parent->_left = subLR; 9 if (subLR) 10 subLR->_parent = parent; 11 12 Node* grandparent = parent->_parent; // parent的父节点(祖父节点) 13 14 // 步骤2:将parent作为subL的右子树 15 subL->_right = parent; 16 parent->_parent = subL; 17 18 // 步骤3:链接subL与祖父节点(或更新根节点) 19 if (parent == _root) 20 { 21 // parent是根节点,则subL成为新根 22 _root = subL; 23 subL->_parent = nullptr; 24 } 25 else { 26 // parent是祖父节点的左/右孩子,对应链接subL 27 if (grandparent->_left == parent) 28 grandparent->_left = subL; 29 else 30 grandparent->_right = subL; 31 32 subL->_parent = grandparent; 33 } 34 35 // 步骤4:重置平衡因子(旋转后子树平衡,BF均为0) 36 parent->_bf = subL->_bf = 0; 37} 38
4.2 左单旋(RotateL):右子树过高(父 BF=2,子 BF=1)
- 下图中展示的是10为根的树,有a/b/c抽象为三颗高度为h的子树(h>=0),a/b/c均符合AVL树的要求。10可能是整颗树的根,也可能是一整颗树中局部的子树的根。这里a/b/c是高度为h的子树,是一种概括抽象表示,他代表所有左单旋的场景,实际左单旋场景有很多,具体跟上面的右旋类似。

- 在a子树中插入一个新结点,导致a子树的高度从h变成h+1,不断向上更新平衡因子,导致10的平衡因子从1变成2,10为根的树左右高度差超过1,违反平衡规则。10为根的树右边太高了。需要往左边旋转,控制两颗树的平衡。
旋转核心步骤:
- 记录父节点(
parent)的右子树(subR)和 subR 的左子树(subRL); - 将 subRL 作为 parent 的右子树(若 subRL 非空,更新其 parent 指针);
- 将 parent 作为 subR 的左子树,更新 parent 的 parent 指针(提前存一下祖父结点,再更新);
- 链接 subR 与祖父节点(或更新_root);
- 重置 parent 和 subR 的平衡因子为 0。
代码实现(注意看注释):
1public: 2void RotateL(Node* parent) 3{ 4 Node* subR = parent->_right; // parent的右子树(即将成为新根) 5 Node* subRL = subR->_left; // subR的左子树(需转移给parent) 6 7 // 步骤1:将subRL作为parent的右子树 8 parent->_right = subRL; 9 if (subRL) 10 subRL->_parent = parent; 11 12 Node* grandparent = parent->_parent; // parent的父节点 13 14 // 步骤2:将parent作为subR的左子树 15 subR->_left = parent; 16 parent->_parent = subR; 17 18 // 步骤3:链接subR与祖父节点(或更新根节点) 19 if (parent == _root) 20 { 21 _root = subR; 22 subR->_parent = nullptr; 23 } 24 else 25 { 26 if (grandparent->_left == parent) 27 grandparent->_left = subR; 28 else 29 grandparent->_right = subR; 30 31 subR->_parent = grandparent; 32 } 33 34 // 步骤4:重置平衡因子 35 parent->_bf = subR->_bf = 0; 36} 37
4.3 左右双旋(RotateLR):左子树的右子树过高(父 BF=-2,子 BF=1)
- 我们通过下面这两种场景图,可以看出,左边高时,如果插入位置不是在a子树,而是插入到b子树,b子树高度从h变成h+1,引发了旋转,右单旋无法解决问题,右单旋过后,我们的树依旧不平衡。右单旋解决的纯粹的左边高,但是插入到b子树中,10为根的子树不再是单纯的左边高,对于10是左边高,但是对于5是右边高,需要用两次旋转才能解决,以5为旋转点进行一个左单旋,以10为旋转点进行一个右单旋,这颗树就平衡了。


- 上面两个图片分别为左右双旋中
h==0和h==1的具体场景分析,下面我们将a/b/c子树抽象为高度h的AVL子树进行分析,另外我们需要把b子树的细节进一步展开为8和左子树高度为h-1的e和f子树,因为我们要对b的父亲5为旋转点进行左单旋,左单旋需要动b树中的左子树。b子树中新增结点的位置不同,平衡因子更新的细节也不同,通过观察8的平衡因子不同,这里我们要分三个场景讨论。
旋转核心步骤:
- 对父节点的左子树(subL)执行左单旋(将 subL 的右子树 subLR 提升为 subL 的父节点);
- 对父节点(parent)执行右单旋(将修正后的 subL 提升为 parent 的父节点);
- 根据 subLR 的原始平衡因子,重置三个节点(parent、subL、subLR)的平衡因子,三种场景。
场景1:h>=1时,新增结点插入在e子树,e子树高度从h-1变为h并不断更新subLR->subL->parent平衡因子,引发旋转,其中subLR的平衡因子为 -1,旋转后subLR和subL平衡因子为0,parent的平衡因子为1。场景2:h>=1时,新增结点插入在f子树,f子树高度从h-1变为h并不断更新subLR->subL->parent平衡因子,引发旋转,其中subLR的平衡因子为1,旋转后subLR和parent的平衡因子为0,subL的平衡因子为-1。场景3:h==0时,a/b/c都是空树,b自己就是一个新增结点,不断更新subL->parent平衡因子,引发旋转,其中subLR的平衡因子为0,旋转后subLR,subL和parent的平衡因子都为0。

代码实现(注意看注释):
1void RotateLR(Node* parent) 2{ 3 Node* subL = parent->_left; // parent的左子树 4 Node* subLR = subL->_right; // subL的右子树(关键节点,决定平衡因子重置) 5 int bf = subLR->_bf; // 记录subLR的原始BF(用于后续重置) 6 7 // 步骤1:先对subL执行左单旋(修正左子树的失衡) 8 RotateL(parent->_left); 9 // 步骤2:再对parent执行右单旋(修正父节点的失衡) 10 RotateR(parent); 11 12 // 步骤3:根据subLR的原始BF,重置三个节点的平衡因子 13 if (bf == 0) 14 { 15 // 场景3:subLR是新插入节点(BF=0)→ 旋转后三者BF均为0 16 parent->_bf = 0; 17 subL->_bf = 0; 18 subLR->_bf = 0; 19 } 20 else if (bf == 1) 21 { 22 // 场景2:subLR的右子树高(BF=1)→ parent的左子树高,subL的右子树高 23 parent->_bf = 0; 24 subL->_bf = -1; 25 subLR->_bf = 0; 26 } 27 else if(bf==-1) 28 { 29 // 场景1:subLR的左子树高(BF=-1)→ parent的右子树高,subL的左子树高 30 parent->_bf = 1; 31 subL->_bf = 0; 32 subLR->_bf = 0; 33 } 34 else 35 { 36 assert(false); 37 } 38} 39
4.4 右左双旋(RotateRL):右子树的左子树过高(父 BF=2,子 BF=-1)
- 跟左右双旋类似,下面我们将a/b/c子树抽象为高度h的AVL子树进行分析,另外我们需要把b子树的细节进一步展开为12和左子树高度为h-1的e和f子树,因为我们要对b的父亲15为旋转点进行右单旋,右单旋需要动b树中的右子树。b子树中新增结点的位置不同,平衡因子更新的细节也不同,通过观察12的平衡因子不同不同,这里我们要分三个场景讨论。
旋转核心步骤:
- 对父节点的右子树(subR)执行右单旋;
- 对父节点(parent)执行左单旋;
- 根据 subRL 的原始平衡因子,重置三个节点的平衡因子。
- 场景1:h>=1时,新增结点插入在e子树,e子树高度从h-1变为h并不断更新
subRL->subR->parent平衡因子,引发旋转,其中subRL的平衡因子为-1,旋转后parent和subRL平衡因子为0,subR平衡因子为1。 - 场景2:h>=1时,新增结点插入在f子树,f子树高度从h-1变为h并不断更新
subRL->subR->parent平衡因子,引发旋转,其中subRL的平衡因子为1,旋转后subR和subRL的平衡因子为0,parent的平衡因子为-1。 - 场景3:h==0时,a/b/c都是空树,b自己就是一个新增结点,不断更新**
subR->parent** 平衡因子,引发旋转,其中subRL的平衡因子为0,旋转后三者的平衡因子都是0。
- 场景1:h>=1时,新增结点插入在e子树,e子树高度从h-1变为h并不断更新

代码实现(注意看注释):
1void RotateRL(Node* parent) 2{ 3 Node* subR = parent->_right; // parent的右子树 4 Node* subRL = subR->_left; // subR的左子树(关键节点) 5 int bf = subRL->_bf; // 记录subRL的原始BF 6 7 // 步骤1:先对subR执行右单旋(修正右子树的失衡) 8 RotateR(parent->_right); 9 // 步骤2:再对parent执行左单旋(修正父节点的失衡) 10 RotateL(parent); 11 12 // 步骤3:根据subRL的原始BF,重置三个节点的平衡因子 13 if (bf == 0) 14 { 15 // 场景3:subRL是新插入节点 → 三者BF均为0 16 subR->_bf = 0; 17 subRL->_bf = 0; 18 parent->_bf = 0; 19 } 20 21 else if (bf == 1) 22 { 23 // 场景2:subRL的右子树高(BF=1)→ parent的左子树高,subR的左子树高 24 subR->_bf = 0; 25 subRL->_bf = 0; 26 parent->_bf = -1; 27 } 28 else if (bf == -1) 29 { 30 // 场景1:subRL的左子树高(BF=-1)→ parent的右子树高,subR的右子树高 31 subR->_bf = 1; 32 subRL->_bf = 0; 33 parent->_bf = 0; 34 } 35 else 36 { 37 // 异常情况 38 assert(false); 39 } 40} 41
五. AVL树的查找和平衡检查:验证树的正确性
5.1 AVL树的查找:(与二叉搜索树一致,O(logN))
1Node* Find(const K& key) 2{ 3 Node* cur = _root; 4 while (cur) 5 { 6 if (cur->_kv.first < key) 7 { 8 cur = cur->_right; 9 } 10 else if (cur->_kv.first > key) 11 { 12 cur = cur->_left; 13 } 14 else 15 { 16 return cur; 17 } 18 } 19 return nullptr; 20} 21
5.2 AVL 树验证:如何判断实现正确?
为确保 AVL 树实现无误,需验证两个核心条件:二叉搜索树特性 + 高度平衡特性。可添加以下辅助接口(类内补充):
1public: 2 // 对外接口:验证AVL树 3 bool IsBalanceTree() 4 { 5 return _IsBalance(_root); 6 } 7 8 // 对外接口:中序遍历(验证二叉搜索树特性:中序递增) 9 void InOrder() 10 { 11 _InOrder(_root); 12 cout << endl; 13 } 14 15private: 16 void _InOrder(Node* root) 17 { 18 if (root == nullptr) 19 return; 20 _InOrder(root->_left); 21 cout << root->_kv.first << " "; 22 _InOrder(root->_right); 23 } 24
测试代码示例:
1#define _CRT_SECURE_NO_WARNINGS 1 2#include"AVLTree.h" 3 4// 测试AVL树插入与平衡性 5void TestAVL() 6{ 7 // 测试用例1:包含双旋场景(如4,2,6,1,3,5,15,7,16,14) 8 int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 }; 9 AVLTree<int, int> t; 10 for (auto e : a) 11 { 12 t.Insert({ e, e }); 13 } 14 15 // 验证1:中序遍历(应递增) 16 cout << "中序遍历结果:"; 17 t.InOrder(); // 输出:1 2 3 4 5 6 7 14 15 16 18 19 // 验证2:平衡性(应返回true) 20 if (t.IsBalanceTree()) 21 { 22 cout << "AVL树平衡验证通过!" << endl; 23 } 24 else 25 { 26 cout << "AVL树平衡验证失败!" << endl; 27 } 28} 29 30int main() 31{ 32 TestAVL(); 33 return 0; 34} 35

声明:AVL树的删除这里不做讲解,有兴趣的朋友可参考:《殷人昆 数据结构:用面向对象方法与C++语言描述》中讲解。
结尾:
1🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点: 2👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长 3❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量 4⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用 5💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑 6🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解 7技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标! 8
结语:AVL 树以 “平衡因子” 控平衡、“旋转操作” 修失衡,将增删查效率稳定在(O(log N)),是自平衡树的经典范式。其核心魅力在于用简洁的平衡逻辑(高度差≤1)和精准的旋转策略,化解普通二叉搜索树退化的问题,虽高频写场景下旋转开销略逊于红黑树,但对平衡原理的直观呈现,使其成为理解复杂自平衡结构的基石。吃透 AVL 树的平衡传递与旋转细节,不仅能掌握一种高效数据结构,更能建立 “以主动维护换性能稳定” 的思维,为后续进阶打下基础。
✨把这些内容吃透超牛的!放松下吧✨
ʕ˘ᴥ˘ʔ
づきらど
《解构 AVL 树:平衡核心、旋转策略与高效实现技巧》 是转载文章,点击查看原文。


