浅谈平衡二叉树(AVL/红黑树)

55 阅读4分钟

📚 ​​故事背景:图书馆的烦恼与智能书架​

小白是图书管理员,管理着按书号排序的书架(普通二叉搜索树)。
​问题​​:新书《C++ Primer》(书号3)、《算法导论》(书号5)、《Java核心》(书号1)依次上架后,书架退化成“单链表”:

Copy
1 (《Java核心》)  
  \  
    3 (《C++ Primer》)  
      \  
        5 (《算法导论》)

找《算法导论》要翻3次!效率从​​O(log n)退化成O(n)​

​馆长说​​:“我们需要​​自动调整平衡的智能书架​​!”于是诞生了两种方案:


⚖️ ​​一、AVL树:强迫症的完美平衡​

想象一个严格的书架:每层左右高度差必须≤1,否则立即旋转调整

​核心原理​​:

  1. ​平衡因子​​:每个节点记录左子树高 - 右子树高,值只能为-1, 0, 1

  2. ​旋转调整​​:插入/删除后检查平衡因子,若绝对值>1则通过旋转恢复平衡

​旋转的四种情况​​(以插入导致失衡为例) :

​失衡类型​​触发条件​​旋转方式​
​LL型​左子树的左子树插入导致失衡单次右旋
​RR型​右子树的右子树插入导致失衡单次左旋
​LR型​左子树的右子树插入导致失衡先左旋再右旋
​RL型​右子树的左子树插入导致失衡先右旋再左旋

​Java代码实现旋转​​(以LL型右旋为例):

java
Copy
class AVLNode {
    int key;
    AVLNode left, right;
    int height; // 节点高度

    AVLNode(int key) {
        this.key = key;
        this.height = 1;
    }
}

private AVLNode rightRotate(AVLNode y) {
    AVLNode x = y.left;      // 1. 获取左孩子x
    AVLNode T2 = x.right;     // 2. 保存x的右子树T2
    
    x.right = y;             // 3. y成为x的右孩子
    y.left = T2;             // 4. T2成为y的左孩子

    // 更新高度
    y.height = Math.max(height(y.left), height(y.right)) + 1;
    x.height = Math.max(height(x.left), height(x.right)) + 1;
    
    return x; // 5. 返回新的根节点x
}

​AVL树缺点​​:维护成本高,频繁插入/删除时旋转次数多


🔴 ​​二、红黑树:实用主义的近似平衡​

想象一个更聪明的书架:允许左右稍有不均(最长路径≤2倍最短路径),用颜色规则约束调整

​五大核心规则​​:

  1. 节点非红即黑
  2. 根节点必须黑
  3. 叶子节点(NIL空节点)视为黑
  4. ​红节点的子节点必须黑​​(不能有连续红节点)
  5. 从任意节点到叶子路径的​​黑节点数相同​​(黑高一致)

​插入调整策略​​(新节点默认红色):

image.png

​Java代码实现插入调整​​:

java
Copy
class RedBlackNode {
    int key;
    RedBlackNode left, right, parent;
    boolean isRed; // 颜色标记

    RedBlackNode(int key) {
        this.key = key;
        this.isRed = true; // 新节点默认红色
    }
}

private void fixInsert(RedBlackNode node) {
    while (node.parent != null && node.parent.isRed) {
        if (parent == grandparent.left) {
            RedBlackNode uncle = grandparent.right;
            if (uncle != null && uncle.isRed) { // 情况1:叔叔为红
                parent.isRed = false;
                uncle.isRed = false;
                grandparent.isRed = true;
                node = grandparent;
            } else { // 情况2:叔叔为黑
                if (node == parent.right) { // LR型
                    leftRotate(parent);
                    node = parent;
                    parent = node.parent;
                }
                parent.isRed = false;         // 父变黑
                grandparent.isRed = true;      // 祖父变红
                rightRotate(grandparent);      // 右旋祖父
            }
        }
        // 对称情况(省略)
    }
    root.isRed = false; // 根始终为黑
}

⚔️ ​​三、AVL vs 红黑树:如何选择?​

​特性​​AVL树​​红黑树​
​平衡要求​严格(高度差≤1)宽松(最长路径≤2倍最短)
​旋转频率​高(维护成本大)低(O(1)~O(log n))1
​查询速度​更快(树更矮)稍慢(树更高)
​适用场景​读多写少(如数据库索引)写频繁(如Map、进程调度)
​典型应用​Windows NT内核Java HashMap、Linux进程管理15

📌 ​​核心结论​​:

  • ​要极致查询速度 → AVL树​

  • ​要高并发插入删除 → 红黑树​


💡 ​​四、为什么工程更爱红黑树?​

红黑树的优势本质是​​用颜色规则模拟2-3-4树​​:

  • ​红色节点​​ = 与父节点组成2-3-4树中的​​3节点或4节点​

  • ​黑色节点​​ = 普通2节点

这种设计让红黑树在插入时最多2次旋转,删除时最多3次旋转,而AVL树可能需O(log n)次旋转


🌟 ​​终极总结​

  1. ​普通二叉树​​:无序插入会退化成链表,查找效率O(n)

  2. ​AVL树​​:旋转严格维护平衡,适合静态数据集

  3. ​红黑树​​:用红黑规则和少量旋转实现近似平衡,适合动态数据

​面试口诀​​:

查询多用AVL,增删多用红黑;
红黑五大律,三旋定乾坤!

试着运行代码,感受旋转如何让“书架”自动保持平衡。需要深入任何细节(如删除调整),欢迎继续追问!