Skip to content

AVL 树

1569 字约 5 分钟

数据结构平衡树

2025-01-21

在本文中,我将详细介绍 AVL 树这一自平衡二叉查找树的基本概念、核心原理以及其操作过程, 帮助读者理解其如何保持高效的查询、插入和删除操作。 同时,我还将通过代码示例演示 AVL 树的实现,并分析其在实际应用中的优势与局限。 本文适合那些有一定数据结构基础,并希望深入了解自平衡树及其应用的读者。

什么是 AVL 树?

AVL 树是计算机科学中最早被发明的平衡二叉树。 它通过平衡因子 (Balance Factor) 来衡量树的平衡程度,并通过树旋转来维护平衡。

AVL 树得名于它的发明者 G. M. Adelson-Velsky 和 E. M. Landis。

平衡因子

平衡因子是 AVL 树用来衡量自身平衡程度的标准。

一个节点的平衡因子定义为节点左右子树的高度差。 当节点的平衡因子绝对值小于等于11时,称该节点为平衡节点,否则为失衡节点。 AVL 树中所有的节点都是平衡节点。

AVL 树的高度

fhf_h 为高度 hh 的 AVL 树所包含的最少节点数,则

fh={1,h=12,h=2fh1+fh2+1,h3 f_h = \begin{cases} 1, & h = 1 \\ 2, & h = 2 \\ f_{h-1} + f_{h-2} + 1, & h \ge 3 \end{cases}

显然,{fh+1}\{f_h + 1\} 是一个斐波那契数列。我们可以算出它的通项为

fh=5+255(1+52)h+5255(152)h1 f_h = \frac{5+2\sqrt{5}}{5} (\frac{1+\sqrt{5}}{2})^h + \frac{5-2\sqrt{5}}{5} (\frac{1-\sqrt{5}}{2})^h - 1

hh 足够大时

fh5+255(1+52)h f_h \sim \frac{5+2\sqrt{5}}{5} (\frac{1+\sqrt{5}}{2})^h

因此,对于有 nn 个节点的 AVL 树,其高度

hlog1+5255+25n=O(logn) h \le \log_{\frac{1+\sqrt{5}}{2}}{\frac{5}{5+2\sqrt{5}} n} = O (\log n)

因此 AVL 树的高度为 O(logn)O(\log n),始终能保持较好的平衡。

树旋转

在 AVL 树中,插入或删除节点会改变树的高度,产生失衡节点。 这时,我们用树旋转来重新维护节点的平衡。

由于修改前 AVL 的节点平衡因子大小不超过 11,如果平衡被破坏, 则失衡节点的平衡因子只可能是 ±2\pm 2

左旋与右旋

根据旋转方向的不同,我们可以把树旋转分为左旋右旋

左旋是将父节点经旋转变成左儿子,而其原先的右儿子变成新的父节点的过程。

左旋

右旋则与左旋相反,是将父节点变成右儿子,原先的左儿子变成新的父节点的过程,相当于做一次左旋的逆变换。

不同旋转策略的采用

我们自底向上研究节点变化后的 AVL 树,找到第一个失衡节点,称该节点为 node。 该节点必有子节点,称其中高度较大的子节点为 child。

根据 node 与 child 平衡因子情况的不同,我们要采取不同的旋转策略。

失衡节点的平衡因子子节点的平衡因子所属情况应采取的旋转方法
>1\gt 10\ge 0LL右旋
>1\gt 1<0\lt 0LR先左旋后右旋
<1\lt -10\le 0RR左旋
<1\lt -1>0\gt 0RL先右旋后左旋

上表中“右旋”指仅右旋 node ,“先左旋后右旋”则是先左旋 child 后右旋 node,其他同理。

LR 可理解为 node 节点的左子树偏大,而 child 节点的右子树偏大。

示例

LR

在上图中,node 为 10,child 为 3。 其中 node 的左子树较大, child 的右子树较大,属于 LR,因此要先左旋后右旋。

首先,我们对 3 进行一次左旋,得到下面的树结构。

LR-1

实际上,这里我们通过对 child 节点的左旋操作,将 LR 转化成了 LL。

然后,我们再对 10 进行一次右旋,最终得到平衡后的 AVL 树。

LR-2

AVL 树的相关操作

有了对平衡因子树旋转的了解后,要理解 AVL 树的相关操作就非常容易了。

查找

AVL 树的查找不涉及任何节点操作,因此与普通的二叉平衡树无异。这里就不再过多赘述。

插入

AVL 树在进行一次插入操作后可能会产生失衡节点。 我们需要在插入节点的位置向上搜索,找出所有可能的失衡节点,进行树旋转。

一次插入可能会产生多个失衡节点,因此我们要一直向上回溯至根节点,确保所有节点都是平衡节点。 在回溯的同时,也要注意更新节点的平衡因子。

删除

AVL 树的删除也与二叉搜索树类似: 如果被删除的节点是叶子节点,直接将其删除;否则,将节点与其后继节点交换,再删除交换后的节点。

与插入相同,删除也会带来平衡因子的更新,因此需要沿着被删除节点到根节点的路径遍历,重新维护平衡。

代码实现

也可以在我的 Github 仓库上查看,仓库上的实现可能会更新一点。

与红黑树的比较

AVL 树与红黑树都是计算机科学中常见的平衡树类型,因此常被用来比较。

事实上,每一颗 AVL 树都可以被染色成红黑树,但红黑树不一定是 AVL 树。 也就是说,AVL 树的平衡要求比红黑树更严格。 因此,AVL 树查找性能相对较好,但插入和删除效率则会偏低。

AVL 树更适合插入删除次数少、查找次数多的场景,红黑树则更胜任于频繁查删的场景。 Windows NT 内核中就广泛使用了 AVL 树,而 C++ 标准库里的 std::mapstd::set 则是用红黑树实现的。

参考资料