欢迎光临
我们一直在努力

07 AVL 树-.NET教程,评论及其它

建站超值云服务器,限时71元/月

用二叉树的遍历来消除递归。

   前面说了模拟系统函数调用的方法消除递归,现在再说说利用遍历消除递归的方法。

   先看两个递归函数。

   void mergesort(int [] array,int left,int right){

      if(right<=left) return;

      int mid=(left+right)/2;

mergesort(array,left,mid);

mergesort(array,mid+1,right);

merge(array,left,mid,mid+1,right);

}

   void quicksort(int[] array,int left,int right){

if(right<=left) return;

选择枢轴,把它放到正确的位置,并把比它小的放到它的左边,比它大的放到右边;

quicksort(左边的数组);

quicksort(右边的数组);

   }

   这两个递归函数都调用自身两次。这和二叉树的遍历有什么关系呢?

   两次递归把整个代码分成三部分。比如:归并排序。

   part1       int mid=(left+right)/2;

   递归调用1  mergesort(array,left,mid);

   part2      

   递归调用2  mergesort(array,mid+1,right);

   part3       merge(array,left,mid,mid+1,right);

   函数的执行过程是:进入part1(当前节点),进入递归调用1(左孩子),part2(当前节点),进入递归调用2(右孩子),进入part3(当前节点)。

   哦,有点遍历的意思了,但遍历是每个节点访问一次,你都访问3次了!part2是空,就算少一次,还有part1part3呀!先我们不看part1,那么就是一个后序的遍历。

   quicksort比较简单part2part3本来就是空的,所以它是个先序的遍历。

   现在我们来看看为什么mergesort对应一个后续遍历?即为什么part1可以不理睬?

   mergesort的目的就是把输入――array[left….right]中的数从小到大排序并放到array[left…right]中。我们要处理的数据是array数组,而part1int mid=(left+right)/2;array是没有关系的。

 

 

 

 

 

avl

一般书讲avl树,一上来就是插入节点有两种情况(对称又两种),怎么旋转使它平衡;删除又有四种情况,怎么旋转使它平衡。却没有说明为什么插入有两种不平衡的情况?为什么没有34种?

先看插入节点。

首先当然是用bst的插入算法找到插入的位置。另外从根到插入节点路径上所有的节点(即插入节点的祖先们)都可能被用到,所以在查找的过程中要把他们都保存下来。

从被插入节点一直向上走,因为原来的树是平衡的(注意,这里说的平衡是只满足avl性质,即左右子树的高度之差的绝对值不超过1,而不是说树是最优平衡的),所以它遇到的节点的平衡因子只能是0,+1-1。如果遇到的是0,则插入一个节点后它的平衡因子就会变成-1+1,但树的高度加1,所以还要继续向上走。如果遇到的是+1-1,那么可能变成0,+2-2。如果变成0了,那么树还是平衡的,并且树的高度不再增加,所以不用再向走上了。如果变成2,则出现第一个不平衡的节点。

既然不平衡了,那么我们就想办法使它平衡。注意,除了使它平衡外,最好它的高度还不变,这样我们就不用向上走了。

总结一下:我们从被插入节点一直向上走,遇到0就把它变成+1-1(左上是+1,右上是-1),并继续向上走;遇到+1-1则分两种情况:一是变成0,一是变成+2-2。但不管哪种情况都不用再向上走了(变成0树高肯定不变,变成+2-2我们假设在使它平衡的同时还不增加树高,后面我们会发现只是可行的)。

假设向上第一个不平衡的节点(即平衡因子由+1-1变成+2-2的节点)为p,由于对称性我们假设它由+1变成+2。(万一没有节点从+1-1变成+2-2呢?那么有两种情况:有节点从+1-1变成0;一直到了根了。无论哪种情况树都已经平衡了。)

则原来的树一定是下面的形状。图上用括号括起来的是节点的平衡因子。

     p(+1)

    /  \

  (h)   q(0)

       /  \

     (h)  (h)

为什么q的平衡因子是0?因为根据前面的分析,从被插入节点一直向上p是第一个平衡因子是+1-1,其余的都是0

因此插入后又有两种情况:q的左子树变成高为h+1;q的右子树变成高为h+1

我们来分别讨论。

先讨论右子树增高的情况(先看简单的)。插入后的树变成下图的形状。

     p(+2)

    /  \

  (h)   q(+1)

       /  \

     (h)  (h+1)

树不平衡了!哪不平衡?p的右子树q(的右子树)太高了!所以要把它弄上去。当然不能随便弄上去,要保持bst树的性质。这就是所谓的rr旋转。旋转的结果是:

     q(0)

    /   \

  p(0)  (h+1)

  /  \

(h)   (h)

结果很好,比原来还平衡,而且树的高度不变(旋转前后都是h+2)。

再来看左子树增高的情况,如下图。

     p(+2)

    /  \

  (h)   q(-1)

       /  \

    (h+1)  (h)

如果我们还是依葫芦画瓢把q弄上去的话,则会变成下面的树:

     q(-2)

    /   \

  p(+1)  (h)

  /  \

(h)   (h+1)

还是不平衡!因为这次不平衡的元凶不是q的右子树而是左子树,因此我们要把q的左子树的根弄上去。

q的左子树也画出来的树如下:

     p(+2)

    /  \

  (h)   q(-1)

       /   \

     r(+1)  (h)

    /    \

  (h-1)   (h)

注意r-1的情况与+1是一样的;r不能为0,为什么?(因为原来r0,在向上走的过程中被变成了+1-1)。

我们先把r弄上去:

     p(+2)

    /  \

  (h)   r(+2)

       /   \

     (h-1)  q(0)

          /    \

        (h)     (h)

r又太高了,再把它也弄上去:

           r(0)

         /       \

       p(-1)     q(0)

       /   \     /    \

      (h)  (h-1) (h)   (h)

这下总算平衡了,而且经过两次旋转后,树的高度不变,还是h+2

这就是所谓的rl旋转。

除此之外还有对称的lllr旋转。

 

 

 

接着来看avl树删除节点的方法。

在这之前先来复习一下bst删除节点的算法。

分三种情况:删除叶子;被删除的节点有一个孩子;被删除的节点有两个孩子。

删除叶子最简单,直接把它的父亲节点的指针置成空就可以了。如下图:

         15                   15

        /  \    删除16       /   \

       4   20  ——–>      4    20

      /    /                /  

     1   16               1

删除只有一个孩子的节点也不难,把它的父亲指向它的孩子就可以了。

         15                   15

        /  \    删除20       /   \

       4   20  ——–>      4    16

      /    /                /  

     1   16               1

 

删除有两个孩子的节点有点麻烦。有两种方法――归并删除法和拷贝删除法。

归并删除的思路是:先安排被删除节点的左孩子,这和只有一个孩子的节点的删除是一样的;然后把被删除节点的右孩子插入到合适的位置(也就是被删除节点左子树的最右节点)。

        15                      10

      /     \      删除15       /  \

    10      30    ———>      5   11

   /  \     /   \                     \

  5   11  20   40                    12

        \                               \

         12                            30

                                       /  \

                                      20  40

上面的例子说明删除一个节点可能使得树的高度反而变高了。

拷贝删除的思路是:在被删除节点的左子树(或右子树)找到最大也即最右的节点(或最小的节点),左子树最右的节点最多只有一个孩子。把删除有两个孩子的节点转变成删除最多一个孩子的节点。然后把这个节点拷贝到真正被删除的节点里。

        15                        12

      /     \      删除15        /    \

    10      30    ———>     10      30

   /  \     /   \              /  \     /  \

  5   11  20   40           5   11  20   40

        \                            

         12                          

它的好处是树的高度不会增加。而且,真正被删除的节点为根的子树的高度是一定减少的。因为如果删除的是叶子,那么原来高度是1,现在变成0了;如果删除的是只有一个孩子的节点,那么它的子树替代了被删除的节点,也使树的高度少1

avl树的删除中我们会用第二种方法,因为我们不希望在删除节点和树反而变高了。

我们还使按照与插入类似的分析方法。

找到第一个真正被删除的节点(叶子或一个孩子的节点),删除这个节点后这个节点的子树还是平衡的(叶子的子树是空树,一个孩子的节点它的孩子原来就是平衡的)。但删除节点后,以它为根的子树高度减一,这可能导致它的父亲不平衡。

因此我们沿着这个被删除的节点一直向上走。遇到的节点不外乎0,+1-1三种。

如果遇到了0,则把它变成+1-1(向右向上是+1向左向上是-1),树依然平衡,由于这时树的高度不会减少,所以不用再向上走了。

如果遇到了+1-1,又有两种情况:变成0;变成+2-2

如果变成0的话树仍然是平衡的,但树的高度少一,所以还要往上走。

变成+2-2的话树就不平衡了,需要我们通过旋转使之平衡。但平衡的同时能不能保证树的高度不减少呢(这样就不用向上走了)?通过分析我们将会发现有时树的高度会减少的。

由于对称性,我们假设某个节点p+1变成了+2,原因是左子树变矮了。那么p的右孩子q的平衡因子有3种情况(0,+1-1)。

1q的平衡因子为1的如下图:

         p(+1)                    p(+2)                     q(0)

        /    \                    /    \                     /   \

      (h)    q(+1)              (h-1)   q(+1)               p(0)  (h)

            /   \                      /   \               /   \

          (h-1)  (h)                  (h-1)  (h)           (h-1)  (h-1)

      原来的情况                左子树变矮后            q转上去后

通过旋转,树变平衡了,但新树的高度减少了,因此还有继续往上走。

2q的平衡因子为0的情况如下图:

         p(+1)                    p(+2)                     q(-1)

        /    \                    /    \                     /   \

      (h)    q(0)              (h-1)    q(0)               p(+1)  (h)

            /   \                      /   \               /   \

          (h)    (h)                  (h)   (h)           (h-1)  (h)

      原来的情况                左子树变矮后            q转上去后

通过旋转,树平衡了,而且新树的高度不变,不用再向上走了。

3q的平衡因子为-1的情况。

   如果我们还是把q转上去的话,依然是不平衡的,如下图:

         p(+1)                    p(+2)                     q(-2)

        /    \                    /    \                     /   \

      (h)    q(-1)              (h-1)    q(-1)               p(+1)  (h-1)

            /   \                      /   \               /   \

          (h)    (h-1)                (h)   (h-1)          (h-1)  (h)

      原来的情况                左子树变矮后            q转上去后

   原因和插入的rl旋转有些类似,因为q的左子树太高了,所以我们先要把q的左子树r转上来(q下去),然后r再转上去(p下去)。但r的平衡因子可能有三种情况,这会不会影响其它的节点呢?我们分三种情况讨论。

   3.1  r的平衡因子是-1

   如下图:

   p(+1)             p(+2)             p(+2)                   r(0)      

  /     \            /    \             /   \                  /    \      

 (h)    q(-1)      (h-1)   q(-1)       (h-1)  r(+1)          p(0)     q(+1)     

       /   \             /    \            /   \           /  \      /   \

     r(-1)  (h-1)       r(-1)  (h-1)      (h-1)  q(+1)    (h-1) (h-1)  (h-2) (h-1)

     /   \             /   \                  /   \

   (h-1)  (h-2)       (h-1)  (h-2)            (h-2)   (h-1)

  原来的情况        左子树变矮后     r转到q上面后   r转到p上面后

通过两次旋转,树变得平衡了,但树的高度减少。

 

3.2  r的平衡因子是+1

   p(+1)             p(+2)             p(+2)                   r(0)      

  /     \            /    \             /   \                  /    \      

 (h)    q(-1)      (h-1)   q(-1)       (h-1)  r(+2)          p(-1)     q(0)     

       /   \             /    \            /   \           /  \      /   \

     r(+1)  (h-1)       r(+1)  (h-1)      (h-2)  q(0)     (h-1) (h-2)  (h-1) (h-1)

     /   \             /   \                  /   \

   (h-2)  (h-1)       (h-2)  (h-1)            (h-1)   (h-1)

  原来的情况        左子树变矮后     r转到q上面后   r转到p上面后

通过两次旋转,树变得平衡了,但树的高度减少。

 

3.3  r的平衡因子是0

   p(+1)             p(+2)             p(+2)                   r(0)      

  /     \            /    \             /   \                  /    \      

 (h)    q(-1)      (h-1)   q(-1)       (h-1)  r(+1)          p(0)     q(0)     

       /   \             /    \            /   \           /  \      /   \

     r(0)  (h-1)       r(0)  (h-1)      (h-1)  q(0)      (h-1)  (h-1) (h-1)  (h-1)

     /   \             /   \                  /   \

   (h-1)  (h-1)       (h-1)  (h-1)            (h-1)  (h-1)

  原来的情况        左子树变矮后     r转到q上面后   r转到p上面后

通过两次旋转,树变得平衡了,但树的高度减少。

 

从上面的图中可以看出r的平衡因子是什么没有关系,只要把r两次转上去就可以了。

总结一下删除的过程:找到真正被删除的节点,然后从它一直向上走。

如果遇到了0,则把它变成+1-1(向右向上是+1向左向上是-1),且不用再向上走了。

如果遇到了+1-1并且把它变成了0(向左遇到+1,向右遇到-1),继续向上走。

如果遇到了+1-1并且把它(设为p)变成了+2-2(向右遇到+1,向左遇到-1),则分三种情况(我们只讨论了+1,-1类似)。a)p的孩子q+1,把q转上去,继续往上走;b)p的孩子q0,把q转上去,不用上走了;c)p的孩子q-1,把q的孩子r两次转上去,继续往上走。

赞(0)
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com 特别注意:本站所有转载文章言论不代表本站观点! 本站所提供的图片等素材,版权归原作者所有,如需使用,请与原作者联系。未经允许不得转载:IDC资讯中心 » 07 AVL 树-.NET教程,评论及其它
分享到: 更多 (0)