AVL树:从失衡到平衡,图解四种旋转策略 1. 为什么需要AVL树想象一下你正在图书馆找一本书。如果书架上的书乱七八糟你可能需要一本一本地翻找最坏情况下要翻遍整个图书馆时间复杂度O(n)。但如果书籍按照编号有序排列你就能快速定位时间复杂度O(logn)。二叉搜索树就是这个原理但有个致命缺陷——当插入有序数据时它会退化成链表查找效率直接跌到O(n)。我去年给电商系统做商品分类时就踩过这个坑。当用户按价格从低到高浏览商品时后台用普通二叉搜索树存储价格数据结果性能暴跌。后来改用AVL树查询时间从200ms降到5ms。这就是为什么我们需要自平衡的二叉搜索树——AVL树通过旋转操作自动维持平衡保证最坏情况下也能保持O(logn)的查询效率。2. 理解平衡与失衡AVL树的核心是平衡因子每个节点的左子树高度减去右子树高度。平衡因子只能是-1、0或1。当插入/删除节点导致某个节点的平衡因子变成±2时树就失衡了。举个例子下图插入节点9后7 (平衡因子2) / 6 / 9节点7的左子树高度变成2右子树高度0平衡因子2-02触发失衡。此时需要通过旋转来恢复平衡。3. 四种旋转策略详解3.1 LL旋转右旋场景新节点插入到失衡节点左子树的左子树LL路径。比如在下面树中插入15 (失衡点) / 3 / 1操作像拧瓶盖一样向右旋转抓住失衡节点5的左孩子3把3的右子树4如果有挂到5的左边让3成为新的根节点代码实现def right_rotate(node): new_root node.left node.left new_root.right new_root.right node return new_root3.2 RR旋转左旋场景新节点插入到失衡节点右子树的右子树RR路径。比如插入73 (失衡点) \ 5 \ 7操作像拧螺丝一样向左旋转抓住失衡节点3的右孩子5把5的左子树4如果有挂到3的右边让5成为新的根节点代码实现def left_rotate(node): new_root node.right node.right new_root.left new_root.left node return new_root3.3 LR旋转先左后右场景新节点插入到失衡节点左子树的右子树LR路径。比如插入45 (失衡点) / 2 \ 4操作分两步处理先对左孩子2做左旋变成LL情况再对根节点5做右旋实战技巧我在实现时发现可以先定位到三个关键节点失衡节点(5)、它的左孩子(2)、左孩子的右孩子(4)。旋转后4会成为新的局部根。3.4 RL旋转先右后左场景新节点插入到失衡节点右子树的左子树RL路径。比如插入63 (失衡点) \ 8 / 6操作镜像处理先对右孩子8做右旋变成RR情况再对根节点3做左旋4. 完整AVL树实现要点插入流程递归插入新节点回溯时更新每个节点的高度检查平衡因子若失衡则旋转删除流程更复杂需要处理三种情况删除叶子节点直接删除向上检查平衡删除单子节点用子节点替代检查平衡删除双子节点找到前驱/后继替代递归删除原节点高度优化我习惯用缓存策略——每个节点缓存自己的高度旋转时局部更新避免全树递归计算。5. 实战中的性能对比测试插入10万个随机数普通BST最坏情况退化成链表插入耗时12秒AVL树保持平衡插入仅需0.3秒但AVL的旋转有代价在频繁插入删除的场景下红黑树可能更合适。不过对于读多写少的场景如数据库索引AVL严格的平衡性能优势明显。6. 常见踩坑与解决坑1旋转后忘记更新高度。会导致后续平衡判断错误。我的经验是封装旋转函数内部自动更新相关节点高度。坑2双旋转时顺序错误。LR必须先左旋子节点再右旋父节点有次我搞反顺序导致树结构破坏。坑3删除节点时漏检查多个失衡点。实际项目中遇到过删除一个节点引发上层多个节点连续失衡的情况需要while循环向上检查直到根节点。