每日算法 — 使用java实现AVL树:自平衡二叉搜索树与四种旋转操作

AVL树是由苏联数学家Georgy Adelson-Velsky和Evgenii Landis于1962年发明的自平衡二叉搜索树。它在插入和删除操作后通过旋转保持树的平衡,确保任意节点的左右子树高度差不超过1,从而将查找、插入和删除的时间复杂度严格控制在O(log n)。与红黑树相比,AVL树的查询效率更高,适合读多写少的场景。

一、AVL树的核心概念

1.1 平衡因子

AVL树中每个节点维护一个平衡因子(Balance Factor),定义为左子树高度减去右子树高度:

平衡因子 = height(左子树) - height(右子树)

AVL树的平衡条件要求所有节点的平衡因子只能是 -1、0 或 1。当平衡因子的绝对值超过1时,树失去平衡,需要通过旋转操作恢复。

1.2 节点高度

节点的高度定义为从该节点到最远叶子节点的路径上的边数。叶子节点的高度为0,空节点的高度为-1。通过维护高度信息,AVL树可以在O(1)时间内判断是否需要旋转。

二、四种旋转操作

当插入或删除导致失衡时,根据失衡节点与其子节点的位置关系,共有四种旋转方式:

2.1 左旋(RR型失衡)

当失衡节点的右子树的右子树过高时发生。以失衡节点为支点,向左旋转,让右子节点成为新的根节点。

    T                  R
   / \                / \
  L   R     =>      T   RR
     / \           / \
   RL  RR         L  RL

2.2 右旋(LL型失衡)

当失衡节点的左子树的左子树过高时发生。以失衡节点为支点,向右旋转,让左子节点成为新的根节点。

      T                L
     / \             /   \
    L   R    =>    LL     T
   / \                  / \
  LL LR               LR  R

2.3 先左旋后右旋(LR型失衡)

当失衡节点的左子树的右子树过高时发生。先对左子节点左旋,再对失衡节点右旋。

     T                 T                LR
    / \               / \             /    \
   L   R    =>      LR   R    =>    L       T
  / \              / \             / \     / \
 LL LR            L  LRR         LL LRL LRR R
    / \          / \
  LRL LRR       LL LRL

2.4 先右旋后左旋(RL型失衡)

当失衡节点的右子树的左子树过高时发生。先对右子节点右旋,再对失衡节点左旋。

三、完整Java实现

以下是一个完整的、可直接运行的AVL树Java实现,包含插入、删除、查找和四种旋转操作。

import java.util.ArrayList;
import java.util.List;

/**
 * AVL树实现
 * 自平衡二叉搜索树,保证任意节点左右子树高度差不超过1
 */
public class AVLTree<T extends Comparable<T>> {

    /**
     * 树节点定义
     */
    private static class Node<T> {
        T data;           // 节点数据
        Node<T> left;     // 左子节点
        Node<T> right;    // 右子节点
        int height;       // 节点高度
        int size;         // 以该节点为根的子树节点数

        Node(T data) {
            this.data = data;
            this.height = 0;  // 新节点是叶子,高度为0
            this.size = 1;
        }
    }

    private Node<T> root;    // 根节点
    private int count;       // 节点总数

    public AVLTree() {
        this.root = null;
        this.count = 0;
    }

    // ==================== 基础工具方法 ====================

    /**
     * 获取节点高度,空节点返回-1
     */
    private int height(Node<T> node) {
        return node == null ? -1 : node.height;
    }

    /**
     * 获取节点子树大小,空节点返回0
     */
    private int size(Node<T> node) {
        return node == null ? 0 : node.size;
    }

    /**
     * 计算平衡因子
     */
    private int balanceFactor(Node<T> node) {
        return node == null ? 0 : height(node.left) - height(node.right);
    }

    /**
     * 更新节点高度和大小
     */
    private void updateNode(Node<T> node) {
        if (node != null) {
            node.height = 1 + Math.max(height(node.left), height(node.right));
            node.size = 1 + size(node.left) + size(node.right);
        }
    }

    // ==================== 旋转操作 ====================

    /**
     * 右旋操作(LL型失衡)
     * 将失衡节点向左下旋转,左子节点上升为新根
     */
    private Node<T> rotateRight(Node<T> y) {
        Node<T> x = y.left;
        Node<T> t2 = x.right;

        // 执行旋转
        x.right = y;
        y.left = t2;

        // 先更新y的高度,再更新x的高度(因为x是y的父节点)
        updateNode(y);
        updateNode(x);

        return x;  // 新的子树根节点
    }

    /**
     * 左旋操作(RR型失衡)
     * 将失衡节点向右下旋转,右子节点上升为新根
     */
    private Node<T> rotateLeft(Node<T> x) {
        Node<T> y = x.right;
        Node<T> t2 = y.left;

        // 执行旋转
        y.left = x;
        x.right = t2;

        // 先更新x的高度,再更新y的高度
        updateNode(x);
        updateNode(y);

        return y;  // 新的子树根节点
    }

    /**
     * 重新平衡节点
     * 根据平衡因子判断失衡类型并执行相应旋转
     */
    private Node<T> rebalance(Node<T> node) {
        updateNode(node);
        int bf = balanceFactor(node);

        // 左子树过高(LL或LR)
        if (bf > 1) {
            if (balanceFactor(node.left) < 0) {
                // LR型:先对左子节点左旋,变为LL型
                node.left = rotateLeft(node.left);
            }
            // LL型:右旋
            return rotateRight(node);
        }

        // 右子树过高(RR或RL)
        if (bf < -1) {
            if (balanceFactor(node.right) > 0) {
                // RL型:先对右子节点右旋,变为RR型
                node.right = rotateRight(node.right);
            }
            // RR型:左旋
            return rotateLeft(node);
        }

        return node;  // 无需旋转,已平衡
    }

    // ==================== 插入操作 ====================

    /**
     * 向AVL树中插入元素
     * 插入后沿路径向上重新平衡
     */
    public void insert(T data) {
        if (data == null) {
            throw new IllegalArgumentException("不能插入null值");
        }
        root = insert(root, data);
        count++;
    }

    private Node<T> insert(Node<T> node, T data) {
        // 标准BST插入
        if (node == null) {
            return new Node<>(data);
        }

        int cmp = data.compareTo(node.data);
        if (cmp < 0) {
            node.left = insert(node.left, data);
        } else if (cmp > 0) {
            node.right = insert(node.right, data);
        } else {
            // 重复元素:这里选择不插入(可根据需求改为计数或覆盖)
            count--; // 补偿外部count++
            return node;
        }

        // 插入后重新平衡
        return rebalance(node);
    }

    // ==================== 删除操作 ====================

    /**
     * 从AVL树中删除元素
     * 删除后沿路径向上重新平衡
     */
    public boolean delete(T data) {
        if (data == null || root == null) {
            return false;
        }
        int oldCount = count;
        root = delete(root, data);
        return count < oldCount;
    }

    private Node<T> delete(Node<T> node, T data) {
        if (node == null) {
            return null;
        }

        int cmp = data.compareTo(node.data);
        if (cmp < 0) {
            node.left = delete(node.left, data);
        } else if (cmp > 0) {
            node.right = delete(node.right, data);
        } else {
            // 找到要删除的节点
            count--;

            // 叶子节点或只有一个子节点
            if (node.left == null) {
                return node.right;
            } else if (node.right == null) {
                return node.left;
            }

            // 有两个子节点:找到后继(右子树最小值)
            Node<T> successor = findMin(node.right);
            node.data = successor.data;
            node.right = delete(node.right, successor.data);
            count++; // delete successor时会减1,这里补偿
        }

        // 删除后重新平衡
        return rebalance(node);
    }

    /**
     * 查找子树中的最小节点
     */
    private Node<T> findMin(Node<T> node) {
        while (node.left != null) {
            node = node.left;
        }
        return node;
    }

    // ==================== 查找操作 ====================

    /**
     * 查找元素是否存在
     */
    public boolean contains(T data) {
        return find(root, data) != null;
    }

    private Node<T> find(Node<T> node, T data) {
        while (node != null) {
            int cmp = data.compareTo(node.data);
            if (cmp == 0) {
                return node;
            } else if (cmp < 0) {
                node = node.left;
            } else {
                node = node.right;
            }
        }
        return null;
    }

    // ==================== 其他查询方法 ====================

    /**
     * 获取树的高度
     */
    public int height() {
        return height(root);
    }

    /**
     * 获取节点总数
     */
    public int size() {
        return count;
    }

    /**
     * 中序遍历(升序输出)
     */
    public List<T> inOrder() {
        List<T> result = new ArrayList<>();
        inOrder(root, result);
        return result;
    }

    private void inOrder(Node<T> node, List<T> result) {
        if (node != null) {
            inOrder(node.left, result);
            result.add(node.data);
            inOrder(node.right, result);
        }
    }

    /**
     * 按层打印树结构(用于可视化调试)
     */
    public void printTree() {
        if (root == null) {
            System.out.println("空树");
            return;
        }
        printTree(root, "", true);
    }

    private void printTree(Node<T> node, String prefix, boolean isTail) {
        if (node == null) return;

        System.out.println(prefix + (isTail ? "└── " : "├── ") +
            node.data + " [h=" + node.height + ", bf=" + balanceFactor(node) + "]");

        List<Node<T>> children = new ArrayList<>();
        if (node.left != null) children.add(node.left);
        if (node.right != null) children.add(node.right);

        for (int i = 0; i < children.size(); i++) {
            boolean isLast = (i == children.size() - 1);
            printTree(children.get(i), prefix + (isTail ? "    " : "│   "), isLast);
        }
    }

    // ==================== 主程序:演示与测试 ====================

    public static void main(String[] args) {
        AVLTree<Integer> tree = new AVLTree<>();

        System.out.println("=== AVL树插入演示 ===");
        int[] insertValues = {30, 20, 40, 10, 25, 35, 50, 5, 15, 27};
        for (int val : insertValues) {
            System.out.println("\n插入: " + val);
            tree.insert(val);
            tree.printTree();
            System.out.println("树高度: " + tree.height() +
                ", 节点数: " + tree.size() +
                ", 平衡因子范围: [-1, 1]");
        }

        System.out.println("\n=== 中序遍历结果 ===");
        System.out.println(tree.inOrder());

        System.out.println("\n=== AVL树删除演示 ===");
        System.out.println("删除 20(有两个子节点的内部节点)");
        tree.delete(20);
        tree.printTree();

        System.out.println("\n删除 5(叶子节点)");
        tree.delete(5);
        tree.printTree();

        System.out.println("\n=== 查找测试 ===");
        System.out.println("包含 25? " + tree.contains(25));
        System.out.println("包含 100? " + tree.contains(100));

        // 大规模性能测试
        System.out.println("\n=== 大规模性能测试(10万元素) ===");
        AVLTree<Integer> bigTree = new AVLTree<>();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            bigTree.insert((int)(Math.random() * 1000000));
        }
        long insertTime = System.currentTimeMillis() - start;
        System.out.println("插入10万个随机元素耗时: " + insertTime + "ms");
        System.out.println("树高度: " + bigTree.height() + " (理论最优: ~17)");
        System.out.println("节点数: " + bigTree.size());

        start = System.currentTimeMillis();
        int found = 0;
        for (int i = 0; i < 100000; i++) {
            if (bigTree.contains((int)(Math.random() * 1000000))) {
                found++;
            }
        }
        long searchTime = System.currentTimeMillis() - start;
        System.out.println("10万次查找耗时: " + searchTime + "ms, 命中次数: " + found);
    }
}

四、复杂度分析

操作 时间复杂度 空间复杂度 说明
查找 O(log n) O(1) 树高严格控制在O(log n)
插入 O(log n) O(log n) 递归栈深度为树高
删除 O(log n) O(log n) 最坏情况需要一次旋转
旋转 O(1) O(1) 仅修改少量指针

AVL树的高度上限约为 1.44 * log₂(n + 2) - 0.328,这意味着包含10万个节点的AVL树高度不超过24,查找操作最多只需24次比较。

五、AVL树与红黑树的对比

特性 AVL树 红黑树
平衡条件 左右子树高度差 ≤ 1 从根到叶子的最长路径 ≤ 2倍最短路径
查找效率 更优(树高更小) 略低(树高最多2倍于AVL)
插入旋转次数 最多2次 最多2次
删除旋转次数 最多O(log n)次 最多3次
适用场景 读多写少(查询频繁) 读写均衡(Java TreeMap/TreeSet)
实现复杂度 稍高 稍低

六、应用场景

AVL树在以下场景中表现优异:

  • 数据库索引:当查询远多于更新时,AVL树的低高度带来更快的检索速度
  • 内存中的有序集合:需要频繁进行范围查询和顺序访问的场景
  • 编译器符号表:标识符查找远多于插入删除的场景
  • 实时系统:严格的时间复杂度保证对确定性要求高的场景

七、总结

AVL树通过维护节点高度和四种旋转操作,确保了二叉搜索树的严格平衡。理解LL、RR、LR、RL四种失衡类型及其对应的旋转策略,是掌握自平衡树的关键。虽然Java标准库选择了红黑树作为TreeMapTreeSet的底层实现,但AVL树在查询密集型应用中仍具有不可替代的优势。