AVL 树(高度平衡二叉搜索树)

105 阅读9分钟

AVL 树是最早提出的严格自平衡二叉搜索树(BST) ,由 Adelson-Velsky 和 Landis 在 1962 年提出。其核心特征是「任意节点的左右子树高度差的绝对值 ≤ 1」,通过严格的平衡约束,将树的高度严格控制在 O(logn)(n 为节点数),保证查找、插入、删除操作的时间复杂度稳定为 O(logn)

与红黑树(近似平衡)相比,AVL 树的「严格平衡」带来了更高的查找效率,但插入 / 删除时的平衡调整成本更高(旋转次数更多),因此适用于「查询密集、修改稀疏」的场景。

一、AVL 树的核心定位

1. 解决的问题:普通 BST 的退化

普通 BST 若按有序序列插入(如 1→2→3→4→5),会退化为单链表,树高变为 n,操作复杂度从 O(logn) 退化为 O(n)。AVL 树通过「严格平衡规则 + 旋转调整」彻底解决这一问题。

2. AVL 树的核心优势与劣势

维度优势劣势
平衡程度严格平衡(高度差≤1),树高更矮平衡调整成本高(插入 / 删除旋转次数多)
查找效率更高(树高≈1.44×log₂(n+1),比红黑树矮)
修改效率更低(最多需 O (logn) 次旋转)
适用场景查询密集(如数据库索引、缓存)插入 / 删除频繁的场景(不如红黑树)

二、AVL 树的基础定义

1. 核心概念

(1)节点高度(Height)

  • 定义:从该节点到其最远叶子节点的路径长度(叶子节点高度为 0,空节点高度为 -1,简化平衡因子计算);
  • 作用:用于计算「平衡因子」,判断节点是否失衡。

(2)平衡因子(Balance Factor, BF)

  • 定义:BF(node) = 左子树高度 - 右子树高度
  • AVL 树的平衡规则:任意节点的 BF ∈ {-1, 0, 1},若 BF 超出此范围(即 |BF| > 1),则节点「失衡」,需通过旋转调整。

2. 节点结构(简化版)

AVL 树节点比普通 BST 多「高度」字段,用于实时计算平衡因子:

template <typename T>
struct AVLNode {
    T key;            // 节点值(键)
    AVLNode* left;    // 左孩子指针
    AVLNode* right;   // 右孩子指针
    int height;       // 节点高度(叶子节点初始为 0)

    // 构造函数
    AVLNode(T k) : key(k), left(nullptr), right(nullptr), height(0) {}
};

3. 辅助函数(高度 / 平衡因子计算)

// 获取节点高度(空节点返回 -1)
template <typename T>
int getHeight(AVLNode<T>* node) {
    return node == nullptr ? -1 : node->height;
}

// 计算节点平衡因子
template <typename T>
int getBalanceFactor(AVLNode<T>* node) {
    if (node == nullptr) return 0;
    return getHeight(node->left) - getHeight(node->right);
}

// 更新节点高度(基于左右子树高度)
template <typename T>
void updateHeight(AVLNode<T>* node) {
    node->height = max(getHeight(node->left), getHeight(node->right)) + 1;
}

三、AVL 树的核心操作:旋转(平衡调整的唯一手段)

AVL 树的旋转分为「单旋转」(左旋 / 右旋)和「双旋转」(左右旋 / 右左旋),所有旋转的核心目标是:调整节点位置,恢复失衡节点的平衡因子(|BF| ≤ 1),且不破坏 BST 特性(左小右大)

1. 单旋转:左旋(Left Rotate)

触发场景:右子树过高(RR 型失衡,后文详解)。

旋转逻辑:

以失衡节点 x 为轴,将其右孩子 y 「提上来」作为新父节点,x 变为 y 的左孩子,y 的原左孩子变为 x 的右孩子。

template <typename T>
AVLNode<T>* leftRotate(AVLNode<T>* x) {
    // 步骤 1:保存关键节点
    AVLNode<T>* y = x->right;    // y 是 x 的右孩子
    AVLNode<T>* T2 = y->left;    // T2 是 y 的左子树

    // 步骤 2:调整指针(核心旋转)
    y->left = x;
    x->right = T2;

    // 步骤 3:更新 x 和 y 的高度(先更底下的 x,再更上层的 y)
    updateHeight(x);
    updateHeight(y);

    // 返回新的根节点(y 取代 x 的位置)
    return y;
}

2. 单旋转:右旋(Right Rotate)

触发场景:左子树过高(LL 型失衡,后文详解)。

旋转逻辑:

与左旋对称,以失衡节点 y 为轴,将其左孩子 x 「提上来」作为新父节点,y 变为 x 的右孩子,x 的原右孩子变为 y 的左孩子。

template <typename T>
AVLNode<T>* rightRotate(AVLNode<T>* y) {
    // 步骤 1:保存关键节点
    AVLNode<T>* x = y->left;     // x 是 y 的左孩子
    AVLNode<T>* T2 = x->right;   // T2 是 x 的右子树

    // 步骤 2:调整指针(核心旋转)
    x->right = y;
    y->left = T2;

    // 步骤 3:更新 y 和 x 的高度
    updateHeight(y);
    updateHeight(x);

    // 返回新的根节点(x 取代 y 的位置)
    return x;
}

3. 双旋转:左右旋(LR Rotate)

触发场景:左子树的右子树过高(LR 型失衡)。

旋转逻辑:先对左孩子左旋,再对失衡节点右旋。

template <typename T>
AVLNode<T>* leftRightRotate(AVLNode<T>* z) {
    // 步骤 1:对 z 的左孩子左旋
    z->left = leftRotate(z->left);
    // 步骤 2:对 z 右旋
    return rightRotate(z);
}

4. 双旋转:右左旋(RL Rotate)

触发场景:右子树的左子树过高(RL 型失衡)。

旋转逻辑:先对右孩子右旋,再对失衡节点左旋。

template <typename T>
AVLNode<T>* rightLeftRotate(AVLNode<T>* z) {
    // 步骤 1:对 z 的右孩子右旋
    z->right = rightRotate(z->right);
    // 步骤 2:对 z 左旋
    return leftRotate(z);
}

四、AVL 树的核心操作:插入(Insert)

插入是 AVL 树的核心操作,逻辑为:先按 BST 插入 → 回溯更新高度 → 检查平衡因子 → 旋转调整失衡节点

1. 插入的完整流程

template <typename T>
AVLNode<T>* insert(AVLNode<T>* root, T key) {
    // 步骤 1:按普通 BST 规则插入新节点
    if (root == nullptr) {
        return new AVLNode<T>(key); // 空树,新建节点作为根
    }
    // 递归插入左/右子树
    if (key < root->key) {
        root->left = insert(root->left, key);
    } else if (key > root->key) {
        root->right = insert(root->right, key);
    } else {
        return root; // 重复值,不插入(AVL 树默认不允许重复)
    }

    // 步骤 2:更新当前节点的高度
    updateHeight(root);

    // 步骤 3:计算平衡因子,判断是否失衡
    int bf = getBalanceFactor(root);

    // 步骤 4:根据失衡类型,旋转调整
    // 情况 1:LL 型失衡(左子树高,且新节点在左子树的左子树)
    if (bf > 1 && getBalanceFactor(root->left) >= 0) {
        return rightRotate(root);
    }
    // 情况 2:LR 型失衡(左子树高,且新节点在左子树的右子树)
    if (bf > 1 && getBalanceFactor(root->left) < 0) {
        return leftRightRotate(root);
    }
    // 情况 3:RR 型失衡(右子树高,且新节点在右子树的右子树)
    if (bf < -1 && getBalanceFactor(root->right) <= 0) {
        return leftRotate(root);
    }
    // 情况 4:RL 型失衡(右子树高,且新节点在右子树的左子树)
    if (bf < -1 && getBalanceFactor(root->right) > 0) {
        return rightLeftRotate(root);
    }

    // 未失衡,返回原节点
    return root;
}

2. 四种失衡类型与旋转对应(核心)

失衡类型平衡因子特征旋转方式示例场景(插入节点导致失衡)
LLBF(root)=2,BF(root.left)=1右旋 root插入节点到 root 左孩子的左子树
LRBF(root)=2,BF(root.left)=-1左旋 root.left → 右旋 root插入节点到 root 左孩子的右子树
RRBF(root)=-2,BF(root.right)=-1左旋 root插入节点到 root 右孩子的右子树
RLBF(root)=-2,BF(root.right)=1右旋 root.right → 左旋 root插入节点到 root 右孩子的左子树

3. 插入示例(LL 型失衡调整)

初始 AVL 树(平衡):

        3 (BF=1, height=1)
       /
      2 (BF=0, height=0)

插入节点 1 → 节点 3 失衡(BF=2):

        3 (BF=2, height=2)  // 失衡
       /
      2 (BF=1, height=1)
     /
    1 (BF=0, height=0)

右旋节点 3 后恢复平衡:

        2 (BF=0, height=1)
       / \
      1   3 (均 BF=0, height=0)

五、AVL 树的核心操作:删除(Delete)

删除比插入更复杂,逻辑为:先按 BST 删除节点 → 回溯更新高度 → 检查平衡因子 → 旋转调整失衡节点(可能递归调整到根)

1. 删除的完整流程

// 辅助:找到 BST 中最小节点(用于双孩子节点删除)
template <typename T>
AVLNode<T>* findMinNode(AVLNode<T>* node) {
    AVLNode<T>* curr = node;
    while (curr->left != nullptr) {
        curr = curr->left;
    }
    return curr;
}

template <typename T>
AVLNode<T>* remove(AVLNode<T>* root, T key) {
    // 步骤 1:按普通 BST 规则删除节点
    if (root == nullptr) {
        return nullptr; // 未找到待删除节点
    }
    // 递归查找待删除节点
    if (key < root->key) {
        root->left = remove(root->left, key);
    } else if (key > root->key) {
        root->right = remove(root->right, key);
    } else {
        // 找到待删除节点,分 3 种情况删除
        // 情况 1:叶子节点/单孩子节点
        if (root->left == nullptr || root->right == nullptr) {
            AVLNode<T>* temp = root->left ? root->left : root->right;
            // 叶子节点(无孩子)
            if (temp == nullptr) {
                temp = root;
                root = nullptr;
            } else { // 单孩子节点
                *root = *temp; // 复制孩子节点数据
            }
            delete temp; // 释放内存
        } else {
            // 情况 2:双孩子节点 → 找中序后继(右子树最小节点)
            AVLNode<T>* temp = findMinNode(root->right);
            root->key = temp->key; // 替换值
            root->right = remove(root->right, temp->key); // 删除后继节点
        }
    }

    // 若树已空,返回 null
    if (root == nullptr) {
        return nullptr;
    }

    // 步骤 2:更新当前节点高度
    updateHeight(root);

    // 步骤 3:计算平衡因子,判断是否失衡
    int bf = getBalanceFactor(root);

    // 步骤 4:旋转调整(与插入的四种失衡类型完全一致)
    // LL 型
    if (bf > 1 && getBalanceFactor(root->left) >= 0) {
        return rightRotate(root);
    }
    // LR 型
    if (bf > 1 && getBalanceFactor(root->left) < 0) {
        root->left = leftRotate(root->left);
        return rightRotate(root);
    }
    // RR 型
    if (bf < -1 && getBalanceFactor(root->right) <= 0) {
        return leftRotate(root);
    }
    // RL 型
    if (bf < -1 && getBalanceFactor(root->right) > 0) {
        root->right = rightRotate(root->right);
        return leftRotate(root);
    }

    // 未失衡,返回原节点
    return root;
}

2. 删除的核心特点

  • 删除后需要从「删除节点的父节点」开始回溯到根,逐个检查平衡因子(插入仅需回溯到第一个失衡节点);
  • 即使当前节点失衡,旋转调整后可能导致其父节点失衡,因此需要递归调整;
  • 旋转逻辑与插入完全一致(四种失衡类型对应相同旋转方式)。

六、AVL 树的查找操作(与普通 BST 一致)

AVL 树的查找逻辑无特殊之处,仅利用 BST「左小右大」的特性,时间复杂度 O(logn)(树高严格控制):

template <typename T>
AVLNode<T>* search(AVLNode<T>* root, T key) {
    if (root == nullptr || root->key == key) {
        return root; // 找到/未找到
    }
    if (key < root->key) {
        return search(root->left, key); // 往左找
    } else {
        return search(root->right, key); // 往右找
    }
}

七、AVL 树 vs 红黑树(核心对比)

特性AVL 树红黑树
平衡标准严格平衡( BF ≤ 1)近似平衡(黑高一致,最长路径 ≤ 2× 最短路径)
树高≈1.44×log₂(n+1)(更矮)≈2×log₂(n+1)(稍高)
插入旋转次数最多 2 次(单次旋转即可修复,最坏递归 1 次)最多 2 次
删除旋转次数最多 O (logn) 次(需回溯到根)最多 2 次
节点存储需额外存储「高度」字段需额外存储「颜色」字段
工业应用数据库索引、缓存(查询密集)C++ STL set/map、Java TreeMap(修改密集)

八、AVL 树的应用场景

  1. 数据库索引:MySQL 的 MEMORY 存储引擎中,索引可基于 AVL 树实现(查询效率优先);
  2. 缓存系统:如 Redis 的有序集合(zset)在元素较少时,曾用 AVL 树(现改为压缩列表 / 跳表);
  3. 编译器符号表:用于快速查找变量 / 函数名(查询远多于修改);
  4. 路由表:网络设备中用于快速查找路由条目(稳定性要求高)。

九、核心总结

  1. AVL 树是「严格平衡」的 BST,核心规则是「任意节点左右子树高度差 ≤ 1」;
  2. 平衡因子是判断失衡的核心,失衡后通过「单旋转 / 双旋转」恢复平衡;
  3. 插入:仅需回溯到第一个失衡节点,旋转 1-2 次即可修复;
  4. 删除:需回溯到根,最多需 O (logn) 次旋转,调整成本高;
  5. 对比红黑树:AVL 树查找更快,修改更慢,适用于「查询密集」场景。