如何在 AVL 树中插入字符串

17 Mar 2025 | 6 分钟阅读

AVL 树是一种优越的数据结构,用于组织字符串并实现快速搜索、插入和删除。平衡 AVL 树可确保任何分支的长度不会比其他分支长太多,从而使这些操作能够以 O(log n) 的时间完成。然而,与数字数据相比,插入字符串会带来独特的挑战,因为字符串比较依赖于字典序而不是数字顺序。在 AVL 树中正确插入字符串需要在搜索插入点时使用正确的比较逻辑,然后利用树旋转来重新平衡结构。本文将提供关于如何充分利用 AVL 树的自平衡能力来插入字符串并保持最佳效率的明确指导。我们将涵盖字符串比较、插入逻辑、高度更新和树旋转等关键主题——掌握这些技术对于在频繁插入后保持字符串 AVL 树的平衡至关重要。

什么是 AVL 树?

AVL 树是高度平衡的二叉搜索树,可实现高效的搜索、插入和删除操作。AVL 树的关键特性是,对于每个节点,左子树和右子树的高度最多相差 1。这确保了树在插入和删除过程中保持大致平衡。

要理解为什么需要平衡,请考虑一个普通的二叉搜索树。随着元素的插入,树可能会变得倾斜,导致一个分支变得非常长。在最坏的情况下,树实际上会变成一个链表,需要 O(n) 的时间进行搜索、插入和删除。

AVL 树在插入或删除后通过树旋转来维持平衡。旋转是一种操作,它在保持二叉搜索树属性(左子树的值 ≤ 当前节点 < 右子树的值)的同时重新排列树中的节点。旋转确保树在重新平衡恢复之前只需向下增长一个节点。

平衡 AVL 树所需的操作会增加插入和删除的轻微开销。但它们允许这些操作平均以 O(log n) 的时间完成。在平衡的 AVL 树中,搜索也需要 O(log n) 的时间。

How to insert Strings into an AVL Tree

影响 AVL 树插入的因素

在 AVL 树中插入元素/字符串时需要考虑的关键因素如下(要点形式):

1. 插入点

  • 遍历树,将每个节点的值与待插入值进行比较
  • 字符串使用字典序进行比较
  • 数字使用标准的“小于/大于”比较
  • 根据比较结果向左或向右遍历
  • 到达空的插入位置

2. 插入元素

  • 将新元素插入到达的空节点

3. 更新高度

  • 从插入节点开始,一直遍历回根节点
  • 在路径上的每个节点,更新其高度
  • 高度 = 左子树和右子树的最大深度 + 1

4. 检查平衡因子

  • 计算每个节点的平衡因子
  • 平衡因子 = 左子树高度 - 右子树高度

5. 执行旋转

  • 如果在插入后平衡因子为 -1、0 或 +1,则进行旋转以恢复平衡
  • 旋转类型
    • 左旋
    • 右旋
    • 左右旋
    • 右左旋
  • 根据该子树的结构选择旋转类型。

6. 向上重复

  • 继续检查/修复平衡,直到到达根节点
  • 现在树已重新平衡,并添加了插入的元素

关键挑战在于正确地比较字符串与数字,在插入后更新高度等元数据,以及在不平衡节点处选择正确的旋转以恢复平衡。管理这些关键步骤对于在插入后保持 AVL 树的效率至关重要。

向 AVL 树插入字符串的算法

插入算法

将字符串插入 AVL 树的主要步骤是:

  • 执行标准的 BST 插入
  • 更新从更新节点到根节点的节点高度
  • 重新平衡任何变得不平衡的子树

要插入,从根节点开始遍历树,在每个节点上按字典序比较字符串。根据比较结果继续向左/右遍历,直到找到一个空节点,然后将新字符串插入那里。

插入后,树可能需要重新平衡。每个节点都维护一个高度属性——它下面的子树的最大深度 + 1。从新节点向上遍历到根节点,更新高度并检查“平衡因子”。

平衡因子是:左子树高度 - 右子树高度

对于 AVL 树,此值必须为 1、0 或 -1。如果不是,则旋转该子树。

有 4 种旋转类型

  • 左旋
  • 右旋
  • 左右旋
  • 右左旋

根据该点的子树结构选择旋转。

示例

考虑将“dog”插入树中

1. 在“cat”处向左遍历(d 在 c 之前)

2. 在“ball”处向右遍历(dog 在 b 之后)

3. 将 dog 插入空节点

4. 从 dog 开始向上更新树的高度

5. 无需旋转。树仍然是 AVL 树!

Python 实现

输出

How to insert Strings into an AVL Tree

说明

  1. TreeNode 类
    • 表示树中的一个节点
    • 包含键、高度、左子节点和右子节点指针
    • key = 存储的数据
    • height = 此节点下子树的最大深度 + 1
    • left / right = 指向左子节点和右子节点的指针
  2. AVLTree 类
    • 包装类,用于表示完整的 AVL 树
    • 包含根节点指针和实用函数
  3. get_height()
    • 接受一个 TreeNode
    • 返回其高度属性(其下子树的最大深度 + 1)
    • 用于计算平衡因子
  4. get_balance()
    • 接受一个 TreeNode
    • 计算其左子树和右子树的高度差
    • 公式为:height(左子树) - height(右子树)
    • 返回计算出的平衡因子
  5. left_rotate() & right_rotate()
    • 这些执行子树上的旋转
    • 接受 TreeNode,它是无平衡子树的根
    • 返回旋转后子树的新根
    • 同时,相应地更新旋转节点的(新)高度
  6. insert()
    • 初始公共插入方法
    • 仅调用执行实际插入的私有 _insert() 方法
  7. _insert()
    • 递归处理完整的插入逻辑
    • 基本情况:在到达 None 节点时插入新节点
    • 根据新键递归地向左/右遍历
    • 插入后,沿路径更新高度并检查平衡因子
    • 调用 left_rotate()、right_rotate() 来修复不平衡
    • 将最终根节点指针返回给父节点
  8. inorder_traversal()
    • 以升序打印键
    • 递归调用 _inorder_traversal()

核心逻辑在于 _insert() 函数以及它如何在插入新键后利用旋转函数来平衡子树。这实现了高效的插入。

结论

将字符串插入 AVL 树需要仔细处理以保持 AVL 数据结构的标志——平衡和效率。遵循正确的字符串比较来定位插入点,添加新节点,更新元数据,并执行任何必要的旋转,可以确保在保持树优化完好的情况下添加字符串。当发生冲突导致需要重新平衡时,左旋、右旋、左右旋或右左旋可以有策略地重新排列节点,以恢复所有分支的平衡。通过仔细考虑字符串的独特属性并利用旋转来平衡新添加的节点,AVL 树即使对于越来越长的字符串也能支持高效的搜索、插入和删除。最终,将字符串处理基础知识与 AVL 平衡树的理解相结合,可以使您的应用程序在处理文本数据时能够扩展和运行。