AVL_SBTree _基本原理

所有平衡树基本由以下三个特征组成:

自平衡条件
旋转操作
旋转的触发
平衡树通过设置合理的自平衡条件,使得二叉排序树的查找、插入等操作的性能不至于退化到 O(n)O(n),并且在进行二叉排序树的查找、插入等操作时进行判断,如果满足其中某个旋转的触发条件,则进行对应的旋转操作。

AVL 树是最早发明的一种平衡树。AVL 树的名称来源于它的两个发明者:G.M. Adelson-Velsky 和 E.M. Landis。他们在 1962 年的论文《An algorithm for the organization of information》中将 AVL 树发表。

我们将从 自平衡条件、旋转操作 和 旋转的触发 这三个方面
介绍 AVL 树的原理。

自平衡条件

AVL 树提出了一个概念:平衡因子(balance factor)。每个结点的平衡因子是指它左子树最大高度和右子树最大高度的差。在 AVL 树中,平衡因子为 -1−1、00、11 的结点都被认为是平衡的,而平衡因子为 -2−2、22 等其他值的结点被认为是不平衡的,需要对这个结点所在子树进行调整。

比如下面这棵树:

对结点 4 来说,平衡因子为 |height_{left} - height_{right}|=|3-1|=2>1∣height
​left
​​ −height
​right
​​ ∣=∣3−1∣=2>1,所以不平衡。

而下面这棵树:

树上的所有结点都满足 AVL 的平衡条件,你如果有兴趣可以自己算一下。

在 AVL 树中,一共有两种单旋操作:左旋和右旋。AVL 树通过一系列左旋和右旋操作,将不平衡的树调整为平衡树。

左旋操作的演示如下:

通过进行左旋操作,使得原先的根 4变成了其左孩子2的右孩子,而 2 原先的左孩子 3变成了 4 的左孩子。“左旋”这个名字是不是很形象呀。

对应的,右旋操作的演示如下:

通过右旋操作,使得原先的根 5变成了其左孩子 3 的右孩子,而 3 原先的右孩子变成了 5 的左孩子。

多旋

AVL 树中还有两种复合旋转操作(即“多旋”),由两次单旋操作组合而成。

第一种是左旋加右旋:

第二种是右旋加左旋:

旋转的触发

插入操作

在插入一个元素后不断回溯的过程中,如果因此导致结点不平衡,则根据不平衡情况(一定是一边子树比另一边子树的高度大 2)进行对应的旋转:

左子树比右子树的高度大 2:

如果新增元素插入到左儿子的左子树中,则进行右旋操作。(LL 型调整)
如果新增元素插入到左儿子的右子树中,则进行左旋加右旋操作。(LR 型调整)
右子树比左子树的高度大 2:

如果新增元素插入到右儿子的右子树中,则进行左旋操作。(RR 型调整)
如果新增元素插入到右儿子的左子树中,则进行右旋加左旋操作。(RL 型调整)

删除操作

类似的,在删除一个元素后不断回溯的过程中,如果因此导致结点不平衡,则和插入操作采用相同的调整操作,确保在删除以后整棵树依然是平衡的。

除了 AVL 树,还有众多的平衡树,比较著名的有红黑树、Treap、替罪羊树等。我们接下来的课程,则要着重介绍另外一种非常年轻的平衡树——Size Balanced Tree。

和 AVL 树类似,我们依然从 自平衡条件、旋转操作 和 旋转的触发 这三个方面介绍 SBTree。

自平衡条件

还记得 AVL 树的自平衡条件么?在 AVL 树中,任何结点的两个子树的高度最大差别为 1。而对于 SBT,它的自平衡条件会显得稍微复杂一些:对于每个结点 tt,同时满足

size[right[t]]≥ max(size[left[left[t]]],size[right[left[t]]])
size[right[t]]≥max(size[left[left[t]]],size[right[left[t]]])

size[left[t]] ≥max(size[left[right[t]]],size[right[right[t]]])
size[left[t]]≥max(size[left[right[t]]],size[right[right[t]]])

其中 size[t]size[t] 表示 tt 结点所在子树的结点个数。

公式看起来很复杂,形象一点来说,就是每个结点所在子树的结点个数不小于其兄弟的两个孩子所在子树的结点个数。

比如对于这棵排序二叉树

对于结点 3939 来说,就不满足 SBTree 的自平衡条件。

旋转的触发

前面已经为你介绍了平衡树的旋转操作,那么在调整过程中,是如何触发左旋、右旋操作的呢?

在调整过程中,一共有 4 种会触发旋转的情况:

LL 型:size[left[left[t]]] > size[right[t]]size[left[left[t]]]>size[right[t]]:首先对子树 tt 执行右旋操作,旋转以后对 tt 的右子树进行调整,之后再对子树 tt 进行调整。

LR 型:size[right[left[t]]] > size[right[t]]
size[right[left[t]]]>size[right[t]]:首先对 tt 的左子树执行左旋操作,再对 tt 进行右旋操作。之后分别调整结点 tt 的左右子树,最终对结点 tt进行调整。

RR 型:size[right[right[t]]] > size[left[t]]
size[right[right[t]]]>size[left[t]]:首先对 tt 执行左旋操作,旋转以后对 tt 的左子树进行调整,之后再对 tt 进行调整。

RL 型:size[left[right[t]]] > size[left[t]]
size[left[right[t]]]>size[left[t]]:首先对结点 tt 的右子树执行右旋操作,再对 tt 进行左旋操作。之后分别调整 tt 的左右子树,最终对 tt 进行调整。

通过递归的进行调整,我们可以最终让不平衡的 SBTree 恢复平衡状态。可以证明,调整操作的均摊时间复杂度为 O(1)O(1)。

和 AVL 不太一样的是,SBTree 只有在插入时才可能触发调整,而 不需要在删除结点以后进行调整。

从理论上说,SBTree 和 AVL 树相比在均摊时间复杂度上没有区别,每次查询、插入和删除的时间复杂度都为 O(logn)O(logn)。在实际运用中,SBTree 在查询操作较多的情况下会有效率上的优势。加之为了维护平衡性记录了每个结点所在子树大小(即子树内结点个数),相比其他平衡树而言,更便于求解第 kk大元素、或求解元素的秩(rank)等类似问题。

你可能感兴趣的:(平衡树)