数据结构——AVL二叉平衡树
AVL 树是史上第一种自平衡二叉搜索树也是数据结构面试的重中之重。它在普通二叉搜索树BST的基础上解决了退化成链表、查询效率暴跌的致命问题。很多同学只会背概念但不懂四种旋转机制、平衡因子、失衡修复、插入删除逻辑。本文带你彻底吃透 AVL 底层原理、平衡逻辑、全部旋转场景、完整代码实现以及优缺点对比看完彻底搞定平衡树。一、为什么需要 AVL 树BST 的致命缺陷1.1 回顾二叉搜索树 BST 特性二叉搜索树规则左子树所有节点值 根节点右子树所有节点值 根节点左右子树同样满足上述规则理想 BST左右均衡树高 logN查找效率 O(logN)1.2 BST 最大痛点不平衡如果插入数据是有序递增/有序递减BST 会直接退化成单链树1→2→3→4→5→6 全部右斜树高等于节点数 N查找效率直接退化到 O(N)和链表没有区别。AVL 树的诞生目的强制让二叉树保持平衡杜绝链表退化保证查询效率稳定 O(logN)。二、AVL 树核心定义与核心概念2.1 AVL 树定义AVL 树是高度平衡的二叉搜索树满足两个条件首先是一棵合法二叉搜索树满足大小规则任意节点的左右子树高度差绝对值不超过 12.2 核心关键词平衡因子Balance FactorAVL 树判断平衡的唯一标准平衡因子 BF平衡因子 左子树高度 - 右子树高度AVL 合法平衡范围BF ∈ {-1, 0, 1}BF 0左右等高完全平衡BF 1左子树更高左偏BF -1右子树更高右偏BF 2 / -2树失衡必须旋转修复2.3 AVL 核心优势无论怎么插入、删除节点树高永远维持 logN查找、插入、删除时间复杂度稳定 O(logN)不会退化。三、AVL 四大失衡场景与旋转修复核心难点AVL 所有失衡只会出现四种情况对应四种旋转操作是全文最重要的部分。失衡判定最近失衡节点的左右子树高度差超过1。3.1 LL 型失衡 —— 右旋修复场景左子树的左子树插入节点导致左子树过高左左失衡特征父节点 BF2左孩子 BF1修复方式单次右旋旋转逻辑将失衡节点的左孩子顶替自己成为新根将左孩子的原有右子树挂载到失衡节点的左孩子位置更新高度、平衡因子恢复平衡3.2 RR 型失衡 —— 左旋修复场景右子树的右子树插入节点导致右子树过高右右失衡特征父节点 BF-2右孩子 BF-1修复方式单次左旋和右旋完全对称是最简单的两种旋转。3.3 LR 型失衡 —— 先左旋、后右旋场景左子树的右子树插入节点左子树偏高但右侧重左右失衡特征父节点 BF2左孩子 BF-1修复方式双旋转先对左孩子左旋转换成 LL 形态再对失衡根节点右旋恢复平衡3.4 RL 型失衡 —— 先右旋、后左旋场景右子树的左子树插入节点右子树偏高但左侧重右左失衡特征父节点 BF-2右孩子 BF1修复方式双旋转先对右孩子右旋转换成 RR 形态再对失衡根节点左旋恢复平衡旋转口诀直接背LL 右旋RR 左旋LR 先左后右RL 先右后左四、AVL 插入完整流程AVL 插入不是单纯插节点是插入回溯更新高度检测失衡旋转修复的完整闭环。按照 BST 规则找到空位插入新节点递归回溯更新所有祖先节点的树高计算每个祖先的平衡因子判断是否失衡判定 LL / RR / LR / RL 四种场景执行对应旋转修复平衡整棵树重新恢复 AVL 平衡特性核心特点插入最多旋转 2 次即可恢复整树平衡效率极高。五、AVL 删除难点面试高阶考点AVL插入简单、删除麻烦。插入只会影响一条路径的祖先最多两次旋转删除节点可能导致上层连续失衡需要向上一直回溯旋转直到根节点。删除流程BST 标准删除度0/度1/度2节点自下而上更新高度、计算平衡因子遇到失衡节点执行对应旋转继续向上回溯直到根节点平衡因此 AVL 删除比插入慢很多这也是后来红黑树取代 AVL 的核心原因。六、AVL 完整 C 可运行代码含四种旋转#include iostream #include algorithm using namespace std; // AVL树节点 struct AVLNode { int val; int height; AVLNode* left; AVLNode* right; AVLNode(int v) : val(v), height(1), left(nullptr), right(nullptr) {} }; class AVLTree { public: // 获取节点高度 int getHeight(AVLNode* node) { return node ? node-height : 0; } // 获取平衡因子 int getBF(AVLNode* node) { return node ? getHeight(node-left) - getHeight(node-right) : 0; } // 更新节点高度 void updateHeight(AVLNode* node) { if(node) node-height 1 max(getHeight(node-left), getHeight(node-right)); } // 右旋 - LL AVLNode* rightRotate(AVLNode* y) { AVLNode* x y-left; AVLNode* T3 x-right; // 旋转 x-right y; y-left T3; // 更新高度先下后上 updateHeight(y); updateHeight(x); return x; } // 左旋 - RR AVLNode* leftRotate(AVLNode* y) { AVLNode* x y-right; AVLNode* T2 x-left; x-left y; y-right T2; updateHeight(y); updateHeight(x); return x; } // 插入节点 自平衡 AVLNode* insert(AVLNode* root, int val) { // 1. 标准BST插入 if(!root) return new AVLNode(val); if(val root-val) root-left insert(root-left, val); else if(val root-val) root-right insert(root-right, val); else return root; // 重复值不插入 // 2. 更新高度 updateHeight(root); // 3. 计算平衡因子 int bf getBF(root); // 4. 四种失衡判断 // LL if(bf 2 getBF(root-left) 1) return rightRotate(root); // RR if(bf -2 getBF(root-right) -1) return leftRotate(root); // LR if(bf 2 getBF(root-left) -1) { root-left leftRotate(root-left); return rightRotate(root); } // RL if(bf -2 getBF(root-right) 1) { root-right rightRotate(root-right); return leftRotate(root); } return root; } // 中序遍历有序输出 void inOrder(AVLNode* root) { if(!root) return; inOrder(root-left); cout root-val ; inOrder(root-right); } }; int main() { AVLTree tree; AVLNode* root nullptr; // 插入测试数据触发多种旋转 root tree.insert(root,10); root tree.insert(root,20); root tree.insert(root,30); // RR左旋 root tree.insert(root,5); root tree.insert(root,8); // LR双旋 cout AVL中序遍历结果; tree.inOrder(root); return 0; }七、AVL 树优缺点面试必背✅ 优点高度绝对平衡树高最低查询速度最快查找、插入、删除稳定 O(logN)相比普通 BST彻底杜绝链表退化问题❌ 缺点致命平衡条件过于严格容错率极低每次插入、删除极易触发旋转维护成本高删除节点可能需要多次回溯旋转性能损耗大写操作频繁的场景性能差八、AVL 树 vs 红黑树面试终极对比很多面试官最爱问为什么工程中用红黑树不用 AVL 树特性AVL 树红黑树平衡严格度严格平衡高度差≤1弱平衡最长路径≤2倍最短路径查询性能更快树更矮略慢一点插入删除性能差频繁旋转优秀变色多、旋转极少适用场景查多写少读写均衡、写多场景结论数据库索引偶尔用 AVLC map/set、Java TreeMap、内核调度器全部用红黑树。九、全文面试总结直接背诵1. AVL 是高度平衡二叉搜索树任意节点左右子树高度差不超过1依靠平衡因子判断平衡。2. 失衡分为四种LL(右旋)、RR(左旋)、LR(先左后右)、RL(先右后左)。3. 插入最多两次旋转即可平衡删除可能多次回溯旋转维护成本高。4. AVL 查询效率极高但写操作代价大适合查多写少场景。5. 相比于红黑树AVL 平衡过严、旋转过多工程中读写均衡场景优先红黑树。