目录

引言

AVL树的基本概念

常见算法

插入节点

查找节点

删除节点

AVL树的应用场景

1. 数据库索引

2. 符号表

3. 字典和词汇表

4. 动态集合

5. GPS导航系统

6. 计算机辅助设计(CAD)

结论


引言

平衡二叉树(Balanced Binary Tree),特别是AVL树(Adelson-Velsky and Landis tree),是一种自平衡的二叉搜索树。它在每次插入或删除操作之后都会自动调整自身,以确保树的高度保持在尽可能小的状态。这种自我调整机制使得AVL树能够在保持较低的高度的同时,提供高效的查找、插入和删除操作。本文将从AVL树的基本概念出发,逐步介绍其原理、常见算法,并通过具体的Java代码示例来加深理解,最后探讨AVL树在算法中的实际应用场景。

AVL树的基本概念

AVL树是一种特殊的二叉搜索树,其特点是任何节点的两个子树的高度之差最多为1。这意味着AVL树始终保持在相对平衡的状态,从而保证了操作的高效性。

节点定义

class AVLTreeNode {
    int val;
    int height;
    AVLTreeNode left;
    AVLTreeNode right;

    AVLTreeNode(int x) {
        val = x;
        height = 1; // 新节点默认高度为1
    }
}

高度和平衡因子

  • 高度:节点的高度定义为该节点到其最远叶子节点路径上的边数。
  • 平衡因子:节点的平衡因子定义为其左子树的高度减去右子树的高度。

失衡

当我们在一个平衡二叉树上插入一个结点时,有可能会导致失衡,即出现平衡因子绝对值大于1       的结点,如下图,当插入结点后,其中key为53的结点失去了平衡,我们称该结点为失衡结点,我们必须重新调整树的结构,使之恢复平衡。

恢复平衡:

那如何去恢复平衡,使得二叉搜索树依然成立为一棵平衡树?先来看平衡调整的四种类型:

 举个例子:如第一个,当平衡二叉树为AB时,插入一个C结点,使得失衡了,失衡结点为A,此时因为C结点插入的位置为失衡结点的左孩子的左孩子,所以是LL型,以此类推。

为了恢复平衡,针对每一种都不一样,但目的都是调整为平衡的,且不能违背二叉搜索树的性质:如下图是每种失衡类型的解决方法,每个都不太一样,目的都是一样的,把key的值为中等的变为树的根,最小的放在左孩子,做大的放右孩子,通过这一目的,降低树的高度,也不用死记硬背。

 

常见算法

插入节点

插入新节点时,需要按照二叉搜索树的方式找到合适的位置,然后进行必要的旋转操作以恢复AVL树的平衡性。

例题:输入关键字序列(16,3,7,11 ,9,26,18,14,15)给出AVL树:

 

对应Java代码实现:

public class AVLTree {
    private AVLTreeNode root;

    // AVL树节点类
    private static class AVLTreeNode {
        int val;
        int height;
        AVLTreeNode left;
        AVLTreeNode right;

        AVLTreeNode(int x) {
            val = x;
            height = 1; // 新节点默认高度为1
        }
    }

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

    // 获取节点的平衡因子
    private int getBalanceFactor(AVLTreeNode node) {
        if (node == null) {
            return 0;
        }
        return getHeight(node.left) - getHeight(node.right);
    }

    // 更新节点的高度
    private void updateHeight(AVLTreeNode node) {
        node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
    }

    // 右旋
    private AVLTreeNode rotateRight(AVLTreeNode y) {
        AVLTreeNode x = y.left;
        AVLTreeNode T2 = x.right;

        // Perform rotation
        x.right = y;
        y.left = T2;

        // Update heights
        updateHeight(y);
        updateHeight(x);

        return x;
    }

    // 左旋
    private AVLTreeNode rotateLeft(AVLTreeNode x) {
        AVLTreeNode y = x.right;
        AVLTreeNode T2 = y.left;

        // Perform rotation
        y.left = x;
        x.right = T2;

        // Update heights
        updateHeight(x);
        updateHeight(y);

        return y;
    }

    // 插入节点
    public void insert(int value) {
        root = insertRecursive(root, value);
    }

    private AVLTreeNode insertRecursive(AVLTreeNode current, int value) {
        if (current == null) {
            return new AVLTreeNode(value);
        }

        if (value < current.val) {
            current.left = insertRecursive(current.left, value);
        } else if (value > current.val) {
            current.right = insertRecursive(current.right, value);
        } else {
            return current; // Value already exists
        }

        // Update height of this ancestor node
        updateHeight(current);

        // Check balance factor to determine the rotations
        int balanceFactor = getBalanceFactor(current);

        if (balanceFactor > 1) {
            if (value < current.left.val) {
                return rotateRight(current);
            } else if (value > current.left.val) {
                current.left = rotateLeft(current.left);
                return rotateRight(current);
            }
        }

        if (balanceFactor < -1) {
            if (value > current.right.val) {
                return rotateLeft(current);
            } else if (value < current.right.val) {
                current.right = rotateRight(current.right);
                return rotateLeft(current);
            }
        }

        return current;
    }

    // 打印AVL树
    public void printAVLTree() {
        printInOrder(root);
        System.out.println();
    }

    private void printInOrder(AVLTreeNode node) {
        if (node != null) {
            printInOrder(node.left);
            System.out.print(node.val + " ");
            printInOrder(node.right);
        }
    }

    // 主函数
    public static void main(String[] args) {
        AVLTree avlTree = new AVLTree();

        // 插入关键字序列
        avlTree.insert(16);
        avlTree.insert(3);
        avlTree.insert(7);
        avlTree.insert(11);
        avlTree.insert(9);
        avlTree.insert(26);
        avlTree.insert(18);
        avlTree.insert(14);
        avlTree.insert(15);

        // 打印AVL树
        avlTree.printAVLTree(); // 输出:3 7 9 11 14 15 16 18 26
    }
}

查找节点

查找特定值的节点时,从根节点开始,根据值与当前节点值的关系决定向左还是向右。

Java代码实现

boolean contains(int value) {
    return containsRecursive(root, value);
}

private boolean containsRecursive(AVLTreeNode current, int value) {
    if (current == null) {
        return false;
    }

    if (value == current.val) {
        return true;
    }

    return value < current.val
            ? containsRecursive(current.left, value)
            : containsRecursive(current.right, value);
}

删除节点

删除节点时需要考虑三种情况:

  1. 节点是叶子节点。
  2. 节点有一个子节点。
  3. 节点有两个子节点。

Java代码实现

void delete(int value) {
    root = deleteRecursive(root, value);
}

private AVLTreeNode deleteRecursive(AVLTreeNode current, int value) {
    if (current == null) {
        return current;
    }

    if (value < current.val) {
        current.left = deleteRecursive(current.left, value);
    } else if (value > current.val) {
        current.right = deleteRecursive(current.right, value);
    } else {
        // Node with only one child or no child
        if (current.left == null) {
            return current.right;
        } else if (current.right == null) {
            return current.left;
        }

        // Node with two children: Get the inorder successor (smallest in the right subtree)
        current.val = findMinValue(current.right);

        // Delete the inorder successor
        current.right = deleteRecursive(current.right, current.val);
    }

    // If the node has only one child or no child, then it will be returned by above statements.
    if (current == null) {
        return current;
    }

    // Update the height of the current node
    updateHeight(current);

    // Check balance factor to determine the rotations
    int balanceFactor = getBalanceFactor(current);

    // Left Left Case
    if (balanceFactor > 1 && getBalanceFactor(current.left) >= 0) {
        return rotateRight(current);
    }

    // Left Right Case
    if (balanceFactor > 1 && getBalanceFactor(current.left) < 0) {
        current.left = rotateLeft(current.left);
        return rotateRight(current);
    }

    // Right Right Case
    if (balanceFactor < -1 && getBalanceFactor(current.right) <= 0) {
        return rotateLeft(current);
    }

    // Right Left Case
    if (balanceFactor < -1 && getBalanceFactor(current.right) > 0) {
        current.right = rotateRight(current.right);
        return rotateLeft(current);
    }

    return current;
}

// Find the minimum value in a subtree
int findMinValue(AVLTreeNode node) {
    return node.left == null ? node.val : findMinValue(node.left);
}

AVL树的应用场景

1. 数据库索引

AVL树可以用于构建数据库的索引,以加速查询速度。

应用原理

  • 数据库记录的键值存储在AVL树的节点中。
  • 查询时,根据键值在树中查找对应的记录。
  • 插入和删除记录时,自动调整树的平衡,保持较低的高度。

场景描述

在数据库管理系统中,索引是提高查询效率的重要手段之一。当数据库需要频繁地按某个字段进行查询时,可以创建基于该字段的AVL树索引。这样,在执行查询操作时,可以迅速定位到指定键值所在的记录,而不需要全表扫描。AVL树的自平衡特性确保了索引结构的高效性,即使在频繁的插入和删除操作下也能保持良好的性能。

2. 符号表

在编程语言的编译器中,符号表用于跟踪变量声明和类型信息。

应用原理

  • 变量名称作为键值存储在AVL树中。
  • 变量的类型和其他相关信息作为键值对应的数据存储。
  • 编译器在处理源代码时,需要维护一个符号表来跟踪所有的变量声明及其属性。

场景描述

编译器在处理源代码时,需要维护一个符号表来跟踪所有的变量声明及其属性。AVL树可以有效地管理这些信息,因为编译器可以根据变量名称快速查找、插入或更新相应的信息。这有助于编译器在编译过程中进行类型检查和其他优化工作。AVL树的自平衡特性使得符号表能够在处理大型代码库时依然保持高效。

3. 字典和词汇表

在自然语言处理中,AVL树可用于构建词汇表或词典。

应用原理

  • 字符串作为键值存储在AVL树中。
  • 字符串的意义或其他附加信息作为键值对应的数据存储。
  • 在处理文本时,需要快速查找和更新词汇信息。

场景描述

在处理自然语言文本时,需要对文本中的词汇进行统计和分析。AVL树可以用于构建词汇表,其中词汇作为键值,出现频率等信息作为键值对应的数据。这有助于在处理文本时快速查找和更新词汇信息,从而提高文本处理的效率。AVL树的自平衡特性使得词汇表能够在处理大量词汇时依然保持高效。

4. 动态集合

AVL树可以用于实现动态集合,支持动态添加、删除元素并保持有序。

应用原理

  • 集合中的元素作为键值存储在AVL树中。
  • 插入新元素时,保持树的有序性。
  • 删除元素时,调整树以保持AVL树的性质。

场景描述

在需要动态管理一组有序元素的应用场景中,如任务队列或优先级队列,AVL树可以有效地支持元素的插入、删除操作,同时保持集合的有序性。这使得在处理动态变化的数据集合时更加高效和灵活。AVL树的自平衡特性使得动态集合在频繁的操作下依然保持良好的性能。

5. GPS导航系统

AVL树可以用于GPS导航系统中的路径规划算法。

应用原理

  • 路径规划算法需要快速查找和更新路径信息。
  • AVL树可以用于存储地理坐标信息,并快速查找最近的路径节点。

场景描述

在GPS导航系统中,路径规划算法需要快速查找和更新路径信息。AVL树可以用于存储地理坐标信息,并快速查找最近的路径节点。这有助于导航系统在实时更新路线时保持高效。AVL树的自平衡特性使得路径规划算法能够在处理大量地理数据时依然保持良好的性能。

6. 计算机辅助设计(CAD)

AVL树可以用于CAD系统中的图形对象管理。

应用原理

  • 图形对象的ID作为键值存储在AVL树中。
  • 图形对象的属性作为键值对应的数据存储。
  • CAD系统需要快速查找和更新图形对象的信息。

场景描述

在CAD系统中,需要管理大量的图形对象,并且经常需要快速查找和更新这些对象的信息。AVL树可以用于存储图形对象的ID,并快速查找对应的属性信息。这有助于CAD系统在处理复杂的设计时保持高效。AVL树的自平衡特性使得图形对象管理在处理大量数据时依然保持良好的性能。

结论

AVL树作为一种高效的数据结构,在计算机科学中有广泛的应用。通过自平衡机制,AVL树能够在保持较低的高度的同时,提供高效的查找、插入和删除操作。掌握AVL树的概念和相关算法对于深入理解计算机科学的核心知识至关重要。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐