赞
踩
从前面的探讨已经可以知道,普通的二叉查找树的查找操作并不能实现O(log2n)的时间复杂度,因此需要为它添加平衡条件。这包含两个层面的意思:
正如上面所说,严格限制每个节点的左右子树高度相同是不太现实的,我们需要略微放宽条件。下面我们来看AVL树的定义(关于树的深度/高度的定义可查看:树):
可以证明,粗略地说,一个AVL树的高度最多为1.44*log2(N+2)-1.328,但是实际上的高度只略大于log2(N)。根据AVL树的定义,现在我们已经可以准确的判断出一颗二叉查找树是否为AVL树了,不妨看下面的例子:
很显然,左边的是一棵AVL树,而右边的并不是,他的根节点就不满足左右子树高度差为1的条件(其根节点左右子树的高度差为2,左子树高度为2,右子树高度为0)。
清楚了AVL树的定义之后,下一步当然就是实现它。乍一看AVL树的定义,可能会觉得很抽象,不妨将其实现进行分解,这里我们主要关注AVL树的查找、插入(构造一棵AVL树的过程就是不断插入新的节点的过程)、删除操作。
执行插人操作后,只有那些从插入点到根节点的路径上的节点的平衡可能被破坏,因为只有这些节点的子树可能发生变化。我们沿着这条路径,从插入点向根节点更新平衡信息,就可以发现一个节点(如果存在的话),它的新平衡破坏了AVL条件,将其命名为A节点。
由于插入之前树是平衡的,因此A节点的左右子树的高度差为2,可以将其分为四种情况:
仔细观察,可以发现上述四种情况中,1和4、2和3相当于是镜像的操作,因此可以将四种情况简化为两种:
对于同一边的情况,只需要单旋转(只需要旋转一次)就可以恢复平衡;而对于在不同边的情况,则需要双旋转来恢复平衡。
对于插入操作位于同一边的操作,只需要通过一次旋转操作就可以恢复节点的平衡条件。以左-左为例(对A的左儿子的左子树进行一次插入,破坏了平衡条件):
节点A不满足AVL平衡条件,它的左子树的高度比右子树大2。为了恢复树的平衡,我们需要把X上移一层,并把Z下移一层,我们将此操作成为旋转。 如下图所示:
首先,可以证明经过了单次旋转之后,整棵子树达还是二叉查找树:
其次,可以证明经过了单次旋转之后,整棵子树符合AVL平衡条件:
观察旋转之前(左图)的结构,从前面的分析已经可以知道左子树的高度比右子树Z的高度大2,只有这样才可以破坏A的平衡;
由于在X中插入节点之前,树是平衡的,因此A的左子树的高度必定增加了,高度的增加必定是由X引起的,因此,插入后X的高度应该大于Y的高度(否则A的左子树高度不可能增加);
由于A是从插入点向根节点搜索得到的第一个不平衡点,因此AL是平衡的,因此X和Y的高度差最多差1,结合第二点的X高度大于Y,可得X的高度比Y大1;
做一个简单的归纳,定义”水平“为子树的根节点到最深的节点的高度差,L(X)代表X的水平,也就是X的高度加1(AL节点),那么可以有:
L(X) = L(Y) + 1 = L(Z) + 2
观察旋转之后(右图)的结构,旋转操作让X、Y、Z的水平发生了变化:
L(X) = L(X) - 1
L(Y) = L(Y)
L(Z) = L(Z) + 1
对比5和6的层次关系,可以知道旋转之后X、Y、Z均处于同一水平,子树的平衡得到恢复。
对于右-右的情况,可以按照类似的旋转操作进行重新平衡;而对于第二类情况(不同边),则需要双旋转,才可以恢复子树的平衡。
不同边的情况可以用下图进行表示(以左-右为例):
此时,Y成为了破坏A的平衡的罪魁祸首,根据上面的分析可知,单旋转前后Y的层次不变,虽然Z的层次增加了,但X的层次减少了,X与Y形成了新的不平衡条件,因此单旋转并不能解决这种情况,这时候就需要双旋转来恢复平衡条件。
所谓的双旋转,就是指先进行一次单旋转,将子树转化成类似同一边的情况;然后再进行第二次单旋转以恢复整棵子树的平衡。 为方面演示,将子树Y进行展开,双旋转中第一次旋转的流程如下所示:
第一次旋转,相当于以A的左孩子AL为目标节点,进行了一次”右-右“情况的单旋转。可以看到,第一次旋转过后,已经基本等同于2.1中的情况(可能略微有所区别,但不影响结果),因此,只需要像2.1中一样在进行一次单旋转即可恢复平衡。
再回过头去看旋转操作,我们可以发现,**无论是单旋转还是双旋转,旋转过后整棵子树不仅恢复了平衡,子树的层次(即子树的高度)也恢复到了和插入前一样(插入后整棵子树的高度增加了1,旋转后子树高度减1)。**也就是说,旋转操作后,不仅第一个失衡的节点达到了平衡,由于这个局部恢复到了插入前的深度,其上层也一定是平衡的,因此整棵树都恢复了平衡!
在AVL树中删除节点后同样可能会引发失衡,失衡后的情况与插入操作类似,只是变成了被修改(删除)的一边的高度比另一边小2(插入是修改那边子树的高度更高),此时,我们同样可以通过旋转使之恢复平衡。
一般来说,二叉查找树的删除操作会将要删除的节点首先移动到叶子节点上,然后进行删除。同样的,我们只需要从被删除的叶子节点开始,逆向往根节点进行查找,更新途中每个节点的平衡信息,就能找到不平衡的节点。然后对不平衡的点进行旋转操作,以恢复局部的平衡。唯一不同的是,删除操作的平衡不会向上传播,也就是说局部恢复平衡之后,还要继续向上查找更新,如遇到不平衡的节点则继续通过旋转操作使其平衡。
以删除右子树的节点D为例,删除后A的右子树的高度比左子树小2,这时候需要找到A的左孩子AL,当AL左子树的深度不小于其右子树的深度,通过单旋转就可以恢复局部平衡:
这个流程与插入操作的左-左类似,只是在上图这种情况下,旋转后子树整体的高度减1(也可能不变,当AL的左右子树高度相同时),上层节点不一定平衡。
继续以删除右子树的节点D为例,当AL左子树的深度比右子树的深度小1,就需要通过双旋转来恢复平衡, 具体的操作与插入时的十分类似,具体可以参考插入操作中的双旋转示意图。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。