在本教程中,我们将学习平衡二叉树。
特别地,我们将了解这类 树[2] 为何如此有用,并探索其中的三种类型。我们将讨论 AVL 树、红黑树和权重平衡树。每种类型的树都有其自身对“平衡”的定义。
如果树中的每个节点最多拥有两个子节点,我们称这棵树为 二叉树[3]。 一个节点的左子节点及其后代构成该节点的左子树。右子树的定义类似。
虽然适合存储层次化数据,但这种一般形式的二叉树并不能保证快速查找。以在下图中搜索数字 9 为例:

Example binary tree for search illustration
无论我们访问哪个节点,我们都不知道接下来应该遍历左子树还是右子树。这是因为这个树的层次结构并不遵循 的关系。
因此,在最坏的情况下,搜索的时间复杂度为 ,其中 是树中的节点数。
我们通过转向一种特殊类型的二叉树来解决这个问题,称为二叉搜索树(binary search tree, BST)。对于 BST 中的每个节点 , 左子树中的所有节点的值都严格小于 。进一步地, 右子树中的所有节点的值都 。 例如:

Example BST
二叉搜索树维护的这种顺序允许我们在查找过程中进行“剪枝”。 假设我们在搜索目标值 时访问了节点 。我们可以忽略 的左子树,只关注右子树,从而加快搜索速度。下图演示了如何在上述搜索树中找到 :

Search in BST
然而,在最坏的情况下,搜索的时间复杂度仍然是 。 它发生在当我们从一个有序数组构建树时,此时树的高度为 ,并退化为链表。由于插入和删除操作都包含搜索,在最坏情况下,BST 上的所有常见操作的时间复杂度都是 。 因此,树的高度决定了复杂度。这正是平衡树发挥作用的地方。它们是一种特殊类型的二叉搜索树。
平衡树是一种不仅只维护节点之间顺序的搜索树。它还控制其 高度[4],确保在插入或删除后保持 的复杂度。
为此,平衡树必须在添加或删除节点后重新平衡自己。 这会带来计算开销,并使插入和删除算法复杂化。然而,为获得具有快速搜索、插入和删除操作的对数高度的搜索树,这是我们愿意付出的代价。我们不会在本文中介绍重新平衡算法。
此类树有几种类型。它们要求所有节点都保持平衡,但平衡的概念因类型而异。
在 AVL 树[5] 中,如果一个节点的左子树和右子树的高度之差最多为 1,我们称该节点是平衡的。 因此,如果一个根节点为 的搜索树的所有节点在 AVL 意义下都是平衡的,那么它就是一棵 AVL 树(空的搜索树,高度为 0,显然是平衡的):
例如:

AVL tree example
这种平衡定义的一个直接结果是,AVL 树的高度在最坏情况下为 。
当所有兄弟子树的高度都相差 1 时,AVL 树的平衡性最差。 例如:

Worst-case AVL tree
这就是 AVL 树的最坏情况结构。向平衡性最差的 AVL 树中添加一个节点,我们要么得到一个非 AVL 树,要么平衡其中的一个节点。删除节点也是如此。因此,这样的 AVL 树是最小的:不存在比它节点更少但高度相同的 AVL 树。
即使我们交换节点的左右子树,树仍然保持平衡。因此,我们假设左子树的节点更多。然后,如果 是高度为 的最小 AVL 树的节点数,我们有:
根据我们的假设,我们有 ,因此:
高度为 的 AVL 结构只有一个节点,所以 ,并且:
因此,在最不平衡的情况下,AVL 树的高度为 。所以,搜索、插入和删除等操作的时间复杂度为对数级。
红黑树[6](Red-Black Trees, RBTs) 也平衡了兄弟子树的高度。 但是,红黑树区分两种类型的节点:红色节点和黑色节点。红黑树确保从一个节点到其子孙叶子的所有路径都经过相同数量的黑色节点。此外,从一个节点到其叶子的黑色节点数量(不包括该节点)称为该节点的黑色高度。整个红黑树的黑色高度是其根节点的黑色高度。例如(为了节省空间,将 NULL 叶子合并为一个节点):

Red-black tree example
根据定义,红黑树满足以下条件:
有些作者不要求根节点是黑色的,因为我们可以在任何情况下重新涂色一棵树。
红黑树的性质确保:
设 是 的黑色高度。我们首先通过归纳法证明,以 为根的子树至少包含 个内部节点。
基本情况是 ,这意味着 是一个空节点,即一个叶子节点:
所以基本情况成立。在归纳步骤中,我们关注 及其子节点。它们的黑色高度等于 或 ,取决于它们的颜色。根据归纳假设,它们每个至少包含 个节点。因此,以 为根的整个子树至少包含这些节点:
现在,设 是根节点 的高度。由于红色节点只能有黑色子节点,从根到任意叶子的路径中至少有一半的节点必须是黑色的。 因此,根的黑色高度 。
利用关于内部节点的结果,我们得到:
同样,我们得出树的高度随着节点数量的对数增长。
权重平衡树[7](Weight-Balanced Trees, WBTs)不平衡兄弟子树的高度,而是平衡它们的叶子数量。 因此,设 和 为 的子树,并设 。我们说 是平衡的,如果:
我们还要求 的所有后代节点满足相同的条件。这等价于说明存在一个 ,使得对于树中的每个节点 ,以下条件成立:
为了理解原因,让我们回忆一下 并跟随推导过程:
所以,这就是权重平衡树 的递归定义:
这是一个 的 WBT 的示例(每个节点内写着叶子数量):

Weight-balanced tree example
树中叶子节点的总数即为其权重,这也是“权重平衡树”名称的由来。我们将证明,权重平衡树的高度同样被限制在 。
假设 是一棵高度为 的最小权重平衡树,并设 为其叶子节点的数量。根据权重平衡树的定义,我们知道 的子树包含的叶子节点数量最多为父节点的 。此外,子树的高度最多为 。因此,我们有:
由于 , 是树中的节点数,我们有:
所以,权重平衡树的高度也是节点数量的对数级别。
如果我们使用过大的 ,重新平衡可能会变得不可完成。它的值应该 。
如果我们准备使用复杂的自定义重新平衡算法,我们可以使用任意小的 。然而,推荐使用 。
在本文中,我们介绍了三种类型的平衡树。 分别是:AVL 树、红黑树和权重平衡树。通过使用不同的平衡概念,它们都能保证查找、插入和删除操作的 时间复杂度[8] 为 。
然而,这些树在发生更改时必须进行自我重新平衡,以使其高度保持在节点数量的对数级别。额外的工作会增加插入和删除算法的复杂度和耗时。但这种开销是值得的,因为它确保了操作的复杂度保持在 。
参考资料
[1]
Milos Simic: https://www.baeldung.com/cs/author/milossimic
[2]
树: https://www.baeldung.com/cs/tree-structures-differences
[3]
二叉树: https://www.baeldung.com/cs/binary-tree-intro
[4]
高度: https://www.baeldung.com/cs/height-balanced-tree
[5]
AVL 树: https://www.baeldung.com/java-avl-trees
[6]
红黑树: https://www.baeldung.com/cs/red-black-trees
[7]
权重平衡树: https://www.cambridge.org/core/books/advanced-data-structures/D56E2269D7CEE969A3B8105AD5B9254C
[8]
时间复杂度: https://www.baeldung.com/cs/time-vs-space-complexity