news 2026/7/15 1:46:09

AVL树:从失衡到平衡,图解四种旋转策略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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-0=2,触发失衡。此时需要通过旋转来恢复平衡。

3. 四种旋转策略详解

3.1 LL旋转(右旋)

场景:新节点插入到失衡节点左子树的左子树(LL路径)。比如在下面树中插入"1":

5 (失衡点) / 3 / 1

操作:像拧瓶盖一样向右旋转:

  1. 抓住失衡节点5的左孩子3
  2. 把3的右子树4(如果有)挂到5的左边
  3. 让3成为新的根节点

代码实现

def right_rotate(node): new_root = node.left node.left = new_root.right new_root.right = node return new_root

3.2 RR旋转(左旋)

场景:新节点插入到失衡节点右子树的右子树(RR路径)。比如插入"7":

3 (失衡点) \ 5 \ 7

操作:像拧螺丝一样向左旋转:

  1. 抓住失衡节点3的右孩子5
  2. 把5的左子树4(如果有)挂到3的右边
  3. 让5成为新的根节点

代码实现

def left_rotate(node): new_root = node.right node.right = new_root.left new_root.left = node return new_root

3.3 LR旋转(先左后右)

场景:新节点插入到失衡节点左子树的右子树(LR路径)。比如插入"4":

5 (失衡点) / 2 \ 4

操作:分两步处理:

  1. 先对左孩子2做左旋,变成LL情况
  2. 再对根节点5做右旋

实战技巧:我在实现时发现,可以先定位到三个关键节点:失衡节点(5)、它的左孩子(2)、左孩子的右孩子(4)。旋转后4会成为新的局部根。

3.4 RL旋转(先右后左)

场景:新节点插入到失衡节点右子树的左子树(RL路径)。比如插入"6":

3 (失衡点) \ 8 / 6

操作:镜像处理:

  1. 先对右孩子8做右旋,变成RR情况
  2. 再对根节点3做左旋

4. 完整AVL树实现要点

插入流程

  1. 递归插入新节点
  2. 回溯时更新每个节点的高度
  3. 检查平衡因子,若失衡则旋转

删除流程更复杂,需要处理三种情况:

  1. 删除叶子节点:直接删除,向上检查平衡
  2. 删除单子节点:用子节点替代,检查平衡
  3. 删除双子节点:找到前驱/后继替代,递归删除原节点

高度优化:我习惯用缓存策略——每个节点缓存自己的高度,旋转时局部更新,避免全树递归计算。

5. 实战中的性能对比

测试插入10万个随机数:

  • 普通BST:最坏情况退化成链表,插入耗时12秒
  • AVL树:保持平衡,插入仅需0.3秒

但AVL的旋转有代价:在频繁插入删除的场景下,红黑树可能更合适。不过对于读多写少的场景(如数据库索引),AVL严格的平衡性能优势明显。

6. 常见踩坑与解决

坑1:旋转后忘记更新高度。会导致后续平衡判断错误。我的经验是封装旋转函数,内部自动更新相关节点高度。

坑2:双旋转时顺序错误。LR必须先左旋子节点再右旋父节点,有次我搞反顺序导致树结构破坏。

坑3:删除节点时漏检查多个失衡点。实际项目中遇到过删除一个节点引发上层多个节点连续失衡的情况,需要while循环向上检查直到根节点。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/15 1:44:44

跳转汇编指令解读

__asm volatile ( "cpsid i\n" // ① 关闭全局中断(PRIMASK1)"ldr r0, 0xE000ED08\n" // ② R0 VTOR 寄存器的地址(0xE000ED08)"str %1, [r0]\n" // …

作者头像 李华
网站建设 2026/7/15 1:44:18

他山之石可以功玉系列(04): 基于 FPGA 的 LMK04828 时钟芯片自动化配置管理模块设计

摘要 针对高速数据采集系统中时钟芯片 LMK04828 的复杂寄存器配置需求,本文设计了一款纯硬件实现的自动化配置管理模块 clk_cfg_module。该模块采用“三状态机+参数化查找表”架构,集成了多任务硬线仲裁、关键寄存器写后回读校验与异常自锁、PLL 锁定状态实时锁存以及动态分频…

作者头像 李华
网站建设 2026/7/15 1:43:29

Lucene实战:从核心原理到高并发场景下的性能调优

1. Lucene核心原理解析Lucene之所以能成为最流行的全文检索引擎工具包,关键在于其精巧的底层设计。先从一个实际场景说起:假设我们要在100万篇技术文章中快速找到包含"分布式系统"的文档,传统数据库的LIKE查询可能需要几分钟&#…

作者头像 李华
网站建设 2026/7/15 1:43:17

TPS6594-Q1智能PMIC:有限状态机实现可靠电源时序管理

1. 项目概述:为什么我们需要一个“会思考”的电源?在嵌入式系统,尤其是汽车电子、工业控制这类对可靠性要求极高的领域,电源管理从来都不是简单的“通电”和“断电”。想象一下,你设计的系统里有一颗多核SoC、几片DDR内…

作者头像 李华
网站建设 2026/7/15 1:42:47

N皇后问题的遗传算法Python实战:从卡顿600到100皇后稳定收敛

1. 这不是教科书,而是一次真实的GA项目复盘:从Matlab到Python的N皇后实战手记你点开这篇文章,大概率不是为了背诵“遗传算法是模拟生物进化过程的优化方法”这种定义。你真正想搞懂的是:当一个真实问题摆在面前——比如让100个皇后…

作者头像 李华
网站建设 2026/7/15 1:42:01

python ldap search Python LDAP搜索,一招搞定域控信息!比BloodHound还快?

-ad 项目教程1. 项目介绍有关 -ad 的脚本, 是借助特定编写方式而成, 用于借助 LDAP 服务, 从域控制器迅速获取各类信息, 此脚本一般在取得有效的 AD 凭证当下随即启动, 适配于在多种情境处理进程里快速获取信息。主要功能依赖项2. 项目快速启动安装使用 pipx 安装pipx install …

作者头像 李华