在算法与数据结构中对平衡二叉搜索树的AVL树的定义是如下,
a tree is balanced if and only if for every node the heights of its two subtrees differ by at most 1.
Trees satisfying this condition are often called AVL-trees( After their inventors). We shall simply call them balanced trees because this balance criterion appears a most suitable one.
the defination is not only simple. but it also leads to a manageable rebalancing procedure and an average search path length practically identical to that of TBE perfectly balanced tree.
在平衡搜索树中,AVL-tree是最早提出来的。AVL-tree为了保证每次搜索的复杂度O(logn),规定每个节点的两个子树的高度差的绝对值最多为1. 这就是AVL-tree实现中引入的平衡因子,在具体实现中,就可以通过平衡因子来调整树,在失衡情况下,通过旋转来重新达到平衡。
那么在具体的实现中遇到这些难点:
1,BF(balance factor)
BF的调整,对于每个节点,如果每次调整树结构都去重新计算一遍两个子树的高度,那么效率就会大打折扣,绝对不可能通过这种方式实现。
2,树的旋转
在AVL-tree中,是通过旋转来重新达到树的平衡,那么旋转有那么方式。
3,节点的删除
删除叶节点相对简单,但涉及到包含子节点的节点,就相对比较复杂,应该怎么来处理。
树的旋转及BF调整
左旋
a c
/ \ / \
b c => a f
/ \ / \
e f b e
现在假设每个节点的高度为H(node),平衡因子为BF(node),左子树的高度减去右子树的高度。那么如图所示,
H(a)=1+max(H(b),H(c)); BF(a) = H(b)-H(c);
在一棵平衡的AVL-tree中,只有插入和删除操作,才能使树的平衡条件打破。现在主要以插入为主来具体描述下。在AVL-tree中的插入操作,首先就是用这个键值来在AVL树中查询,知道某个节点的叶节点为null,然后就把这个新节点安插在这个为null的叶节点的位置。如果找到,那就说明这个键值存在,插入失败。当安插好这个节点以后,它的父节点和祖先节点的平衡因子就可以变化,当首个祖先节点的平衡因子超出AVL-tree的定义范围(绝对值小于等于1)时,就需要通过旋转来调整这个子树,然后继续沿着父节点回溯到平衡因子满足条件为止,在最差的情况下,会回溯到根节点。
其实旋转根据平衡因子的值可以分为左旋和右旋,当BF<-1 时,只能做左旋,才能恢复平衡,如果右旋,只是加剧失衡而已。那么在调整后平衡因子肯定变化的是调整前失衡的节点(它恢复了平衡),可能变化为失衡节点的右子树(它有可能变得不平衡,需要再度旋转)。当BF>1 时,只能做右旋。同上。
在具体实现中,这个平衡因子就比较关键了,它决定了什么时候旋转,该怎么旋转。而BF在旋转前后的计算也是比较关键。那么分析一下BF在旋转前后都由哪些参数来决定,就能不通过遍历整个子树的高度来决定BF.
假如BF(a)<-1,旋转前的各个参数为h,bf,旋转后的各个参数为H,BF.那么旋转后受影响的平衡因子就是a和c。
bf(a)=h(b)-h(c); BF(a)=H(b)-H(e);
bf(c)=h(e)-h(f); BF(c)=H(a)-H(f);
因为在旋转前后平衡因子受影响的只有节点a,c。其他节点的平衡因子和节点高度是没有发生变化的。
所有h(b)==H(b), h(f)==H(f), h(e)==H(e);
那么在旋转前后a,c节点的BF变化为:
vary(a) = BF(a) – bf(a) = (H(b)-H(e)) – (h(b)-h(c)) = h(c) – h(e);
vary(c) = BF(c) – bf(c) = (H(a)-H(f)) – (h(e)-h(f)) = H(a) – H(e);
由以上可以得到一个基本的结论,在left rotation中,失衡节点(a)的BF调整只与旋转前c,e子树相关。右子节点c的平衡因子和旋转后的a,e子树相关。
如图所示,h(c)=1+max(h(e), h(f)), 当bf(c)<0时,h(c)=1+h(f), so vary(a) = 1+h(f) – h(e) = 1-(h(e) – h(f)) = 1 – bf(c);
当bf(c)>=0时, h(c)=1+h(e), so vary(a) = 1 + h(e) – h(e) = 1;
H(a)=1 + max(H(b), H(e)), 当BF(a)<=0时, H(a)=1+H(e), so vary(c) = 1 + H(e) – H(e) = 1;
当BF(a)>0时,H(a)=1+H(b), so vary(c) = 1+ H(b) – H(e) = 1 + BF(a);
由以上推断可以得出,调整后的a平衡因子由调整前的c的平衡因子决定,调整后的哦c平衡因子由调整后的a平衡因子决定。
BF(a) = bf(a)+vary(a) = bf(a) + 1 – bf(c);
BF(c) = bf(c)+vary(c) = bf(c) + 1 + BF(a);
由以上推断的结果,对于左旋的伪代码就可以这么表述:
//失衡节点先调整
a->bf++;
if(c->bf<0)
a->bf -= c->bf;
//失衡节点右节点后调整
c->bf++;
if(a->bf>0)
c->bf += a->bf;
右旋
a b
/ \ / \
b c => e a
/ \ / \
e f f c
因为左旋和右旋是对称的,所有不用推算也能写出调整后平衡因子的伪代码。
a->bf–;
if(b->bf>0)
a->bf -= b->bf;
b->bf–;
if(a->bf<0)
b->bf += a->bf;
再把过程简单写一下
假如BF(a)>1, 那么如图平衡因子受影响的是节点是a,b。
bf(a)=h(b)-h(c); BF(a)=H(f)-H(c);
bf(b)=h(e)-h(f); BF(b)=H(e)-H(a);
因为在旋转前后,h(e)==H(e), h(f)==H(f), h(c)==H(c);
vary(a) = BF(a) – bf(a) = H(f)-H(c) – (h(b)-h(c)) = -(h(b)-h(f));
vary(b) = BF(b) – bf(b) = H(e)-H(a) – (h(e)-h(f)) = -(H(a)-H(f));
h(b)=1+max(h(e), h(f)), 当bf(b)<=0时,h(b)=1+h(f), vary(a)=-(1+h(f)-h(f))=-1;
当bf(b)>0时,h(b)=1+h(e), vary(a)=-(1+h(e)-h(f)) = -1-bf(b);
H(a)=1+max(H(f), H(c)), 当BF(a)<0时, H(a)=1+H(c), vary(b)=-(1+H(c)-H(f)) = -1+(H(f)-H(c)) = -1 + BF(a);
当BF(a)>=0时, H(a)=1+H(f), vary(b)=-(1+H(f)-H(f)) = -1;
BF(a) = bf(a)+vary(a) = bf(a) -1 – bf(b);
BF(b) = bf(b)+vary(b) = bf(b) -1 + BF(a);
avl树的旋转,当avl树平衡条件打破以后,我们是通过旋转来使avl树重新满足平衡条件。旋转由两个节点参与,也就是失衡节点以及它的一个子节点(由平衡因子来决定是左子节点(bf>0)或者右子节点(bf<0))。所以旋转分为左旋转和右旋转,而他们也是对称的。在插入节点的时候有的地方也会提到双旋转,其实也是由左右两种旋转构成的,双旋转的引入是为了解决单一的左右旋转引起的死循环问题。
关于死循环问题是这样引起的,也就是所谓的内侧插入,因为左右旋转是对称的,所以只拿左旋转来说,当失衡节点平衡因子是-2时,其右子节点bf为1,和失衡节点不同侧,左旋一次,根节点bf为2,左子节点bf为-1,这相当于变成了右旋传的内侧插入,因为左右旋转是对称的,所以插入节点必须和父节点同侧,否则就必须先做一次旋转调节,使他们同侧,才能避免这种死循环的情况出现。