授课语音

什么是平衡树

1. 引言

在计算机科学中,树是一个非常常见的数据结构。它的基本特性是节点与节点之间有层级关系,通过指针相连接。平衡树是一类特殊的二叉搜索树(BST),它在传统二叉搜索树的基础上加入了平衡的概念,确保了树的高度在插入和删除元素时保持较小的增量,从而保证了操作的高效性。平衡树的引入解决了二叉搜索树在极端情况下退化成链表的问题,保证了操作的时间复杂度。


2. 平衡树的定义

平衡树本质上是一个二叉搜索树,其中对于每个节点,它的左子树和右子树的高度差(即平衡因子)必须满足某个条件。通过这种限制,平衡树的高度始终保持在对数级别,因此能够保证较快的查询、插入和删除操作。

常见的平衡树有以下几种:

  1. AVL树:每个节点的左右子树的高度差不能超过1。
  2. 红黑树:通过定义节点的颜色以及一系列规则,保持树的平衡。

这里,我们将详细介绍其中最常见的 AVL 树


3. AVL树的介绍

AVL树(Adelson-Velsky and Landis Tree)是一种自平衡的二叉搜索树,它要求树的每个节点的左右子树的高度差的绝对值不超过1。这个高度差称为“平衡因子”。如果某个节点的平衡因子超过了这个范围,就需要通过旋转操作来恢复平衡。

3.1 平衡因子

对于 AVL 树中的任意节点,其平衡因子定义为:

平衡因子 = 左子树高度 - 右子树高度
  • 如果平衡因子为0,说明左右子树的高度相等。
  • 如果平衡因子为1,说明左子树的高度比右子树大1。
  • 如果平衡因子为-1,说明右子树的高度比左子树大1。

当平衡因子的绝对值大于1时,树就不再平衡,这时需要进行旋转操作来恢复平衡。

3.2 旋转操作

AVL树有四种基本的旋转操作,它们可以用来调整树的结构,使其恢复平衡:

  1. 右旋转(Right Rotation):当左子树太高时,进行右旋转。
  2. 左旋转(Left Rotation):当右子树太高时,进行左旋转。
  3. 左右旋转(Left-Right Rotation):当左子树的右子树比左子树的左子树高时,先进行左旋转,再进行右旋转。
  4. 右左旋转(Right-Left Rotation):当右子树的左子树比右子树的右子树高时,先进行右旋转,再进行左旋转。

4. AVL树的基本操作

4.1 插入操作

插入操作是将一个新元素插入到树中。首先,按照二叉搜索树的规则将元素插入到合适的位置,然后检查树是否平衡。如果插入后某些节点的平衡因子不符合条件,就需要通过旋转操作来恢复树的平衡。

4.2 删除操作

删除操作是从树中删除一个元素。删除元素后,可能导致树的不平衡,因此需要从删除节点开始,逐级向上检查并恢复平衡。

4.3 查询操作

查询操作是查找树中是否包含某个特定元素。AVL树的查询操作和普通的二叉搜索树一样,时间复杂度为O(log n),因为树的高度始终保持在对数级别。


5. 复杂度分析

  1. 查询操作时间复杂度:由于 AVL树的高度始终保持在O(log n),因此查询操作的时间复杂度为O(log n)。
  2. 插入操作时间复杂度:插入操作包括查找位置和可能的旋转操作,时间复杂度为O(log n)。
  3. 删除操作时间复杂度:删除操作同样包括查找位置和旋转操作,时间复杂度为O(log n)。

6. 平衡树的优势

  1. 保证了操作的时间复杂度:通过保持树的平衡,平衡树确保了查找、插入、删除操作的时间复杂度不会退化为O(n),即使在最坏情况下。
  2. 自动维护平衡:与普通的二叉搜索树相比,平衡树能够自动维护平衡,不需要开发者手动干预。

7. 平衡树的缺点

  1. 结构复杂:相比普通的二叉搜索树,平衡树的插入和删除操作相对复杂,需要进行旋转操作。
  2. 额外的空间开销:为了记录节点的高度或平衡因子,平衡树需要额外的空间来存储这些信息。

8. Java代码示例:AVL树的基本操作

class AVLTree {
    class Node {
        int key, height;
        Node left, right;
        
        Node(int d) {
            key = d;
            height = 1;
        }
    }

    private Node root;

    // 获取节点的高度
    private int height(Node N) {
        if (N == null)
            return 0;
        return N.height;
    }

    // 获取平衡因子
    private int getBalance(Node N) {
        if (N == null)
            return 0;
        return height(N.left) - height(N.right);
    }

    // 右旋操作
    private Node rightRotate(Node y) {
        Node x = y.left;
        Node T2 = x.right;

        // 旋转
        x.right = y;
        y.left = T2;

        // 更新高度
        y.height = Math.max(height(y.left), height(y.right)) + 1;
        x.height = Math.max(height(x.left), height(x.right)) + 1;

        return x;
    }

    // 左旋操作
    private Node leftRotate(Node x) {
        Node y = x.right;
        Node T2 = y.left;

        // 旋转
        y.left = x;
        x.right = T2;

        // 更新高度
        x.height = Math.max(height(x.left), height(x.right)) + 1;
        y.height = Math.max(height(y.left), height(y.right)) + 1;

        return y;
    }

    // 插入节点
    public Node insert(Node node, int key) {
        if (node == null)
            return new Node(key);

        // 二叉搜索树插入
        if (key < node.key)
            node.left = insert(node.left, key);
        else if (key > node.key)
            node.right = insert(node.right, key);
        else // 不允许重复元素
            return node;

        // 更新节点高度
        node.height = 1 + Math.max(height(node.left), height(node.right));

        // 检查平衡因子,是否需要旋转
        int balance = getBalance(node);

        // 左左情况
        if (balance > 1 && key < node.left.key)
            return rightRotate(node);

        // 右右情况
        if (balance < -1 && key > node.right.key)
            return leftRotate(node);

        // 左右情况
        if (balance > 1 && key > node.left.key) {
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }

        // 右左情况
        if (balance < -1 && key < node.right.key) {
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }

        return node;
    }

    // 打印树(中序遍历)
    public void inOrder(Node root) {
        if (root != null) {
            inOrder(root.left);
            System.out.print(root.key + " ");
            inOrder(root.right);
        }
    }
}

9. 总结

在今天的课程中,我们学习了平衡树的基本概念以及其中一种常见的实现——AVL树。AVL树通过维护每个节点的平衡因子来确保树的高度保持在对数级别,从而保证了查询、插入和删除操作的高效性。虽然AVL树的实现较为复杂,但它在处理大规模数据时具有不可替代的优势。希望大家通过今天的学习,能够掌握平衡树的基本原理,并能够在实际问题中有效应用它。

去1:1私密咨询

系列课程: