普通平衡树
一般说“普通平衡树”, 应该指的就是最早发明的平衡二叉树 “AVL树”。AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表了它。
为什么发明了平衡树
当我们用二叉树维护信息的时候,要进行插入/删除/查找操作。
二叉树体现了一种二分的思想,因此操作信息的期望复杂度在 log级。
但是二叉树的形态受操作顺序的影响,例如我按照1,2,3…,n的顺序把这n个数插入到二叉树中,按照二叉树的性质,会形成下面的状态:
这个时候二叉树就变成了链表,操作的时间复杂度退化到了O(n),这是人们不希望看到的。其实可以想到,我们不希望二叉树的各个子树的深度相差的特别大,否则就会出现这样看上去 “极不协调” 的样子。
相同结点个数的情况下,我们希望树的形态尽可能的接近满二叉树。
这里有一个细节,对于满二叉树的定义,国内和国外有很大出入,具体见百度百科,我这里引用的是国内的定义。
例如下面,两棵树维护的信息实际上是相同的,但是右边的树的效率显然更高。
红色的是本不存在的虚点。左边的图也有但是数量太多就不画了(2^5-5个)。能看出来左图虚点很多,离理想的满二叉树也很远。
平衡树的平衡因子
前面写道,人们不希望二叉树的各个子树深度相差过大,因为这样会降低操作的效率。即满二叉树是一种最理想的状态,它的各个子树的深度完全相等。但是实际使用当中,受到结点数目的限制,一棵二叉树的各个子树的深度不可能时时刻刻相等。在平衡树中,引入了一种标记信息叫做“平衡因子”,来衡量一棵二叉树的平衡程度。
平衡因子定义为一个结点的左子树的深度减去右子树的深度。
在 “无法保证实际当中的二叉树总是满二叉树” 的情况下,我们允许一个结点的左右子树深度最多相差1,即一个结点的平衡因子的绝对值大于1,则判定该结点失衡。
树旋转操作
插入和删除结点都会改变某些结点的平衡因子,也就有可能会导致失衡。
下面以插入结点为例介绍树旋转操作。
当新结点插入时,从根节点到插入结点 这条路径上的结点的平衡因子会改变,从插入的结点向上找,找到的第一个失衡结点 (如果有的话) 则称这个失衡结点为根的子树为最小失衡子树。
下面记最小失衡子树的根结点为Rs
对于一个引起失衡的新插入结点,他的位置只能有四种情况:
- Rs的左儿子的左子树。(记为LL,下同)
- Rs的左儿子的右子树。(LR)
- Rs的右儿子的右子树。(RR)
- Rs的右儿子的左子树。(RL)
当有结点发生失衡时应当如何调整呢。
下面给出一个例子,在一个空树中依次插入1,2,3 三个点。
首先1作为根结点,2比1大,插入1的右子树,3比1和2大,插入2的右子树。
这样结点1的平衡因子变成了-2,明显是失衡了。
我们想到,所谓“2比1大”,其实反过来说就是 “1比2小”,所以其实可以把1放在2的左子树上,这样其实也是符合二叉搜索树的定义的。而且这样做似乎不会产生失衡。
看上去就像1号结点围绕2号结点,逆时针旋转了一些角度。
这个操作叫 “左旋”,很明显这个例子中,插入结点在最小失衡子树根节点的右儿子的右子树上(RR),这种情况下可以用一次左旋来维持平衡。
下面是其他失衡情况的维持平衡操作:
- LL型失衡:以最小失衡子树的根节点为待旋转结点,进行一次右旋。
- RR型失衡:以最小失衡子树的根节点为待旋转结点一次左旋。
- LR型失衡:先以最小失衡子树根节点的左儿子为待旋转结点,进行一次左旋;再以最小失衡子树的根节点为待旋转结点进行一次右旋。
- RL型失衡:先以最小失衡子树根节点的右儿子为待旋转结点,进行一次右旋;再以最小失衡子树的根节点为待旋转结点进行一次左旋。
注:LR型失衡,在进行进行一次左旋后会变成LL型,所以再进行右旋会回到平衡,RL型与之对称。
旋转的一般形式
上面是一个左旋的简单例子,下面给出旋转的一般形式:
左旋:
- 将待旋转结点的右儿子的左子树(如果有的话)连接在待旋转结点的右子树上。
- 用待旋转结点的右子树,代替待旋转结点的位置,将待旋转结点变成原先它的右儿子的左子树。
右旋:
- 将待旋转结点的左儿子的右子树(如果有的话)连接在待旋转结点的左子树上。
- 用待旋转结点的左子树,代替待旋转结点的位置, 将待旋转结点变成原先它的左儿子的右子树。
给一个标准的RL例子:
写在后面
平衡树(没有特指AVL)我本来想好好写写来着,但是(特指AVL)这个普通平衡树,真正写的时候才发现,这个东西真的很难写明白。有些地方,不像前几篇博客,我能对 “为什么是这样操作” 给出一个起码能把自己说服的说法。
还有就是对于AVL这个数据结构本身,作为 “最早发明的平衡二叉查找树” ,它的价值自然是巨大的。但是随着时代发展,AVL渐渐被其他后出现的,更加简洁的平衡树所取代,用B站里某位大佬的话说,唯一能看到AVL的地方可能只有大学教材了。包括 洛谷P3369 那道题,整整12页的题解,竟然只有一篇是 “老老实实” 地用AVL写的,而且好像还用了面向对象编程(我也不太懂这个)。我企图用AVL过掉此题,断续有三四天吧,最后还是没成功,和别人照猫画虎,写了个 Splay 过了。有两种可能,一是这个东西是真的没法玩了,二是…我太菜了。
普通平衡树例题
真·AVL模板:HNUST OJ 1812 算法9-9~9-12:平衡二叉树的基本操作.
伪·AVL模板:洛谷P3369 【模板】普通平衡树.