AVL重平衡细节——插入

 话说这个系列鸽了好久,之前在准备语言考试,就没管博客了,现在暑假咱们继续上路!

 

每当我们进行一次插入之后,整棵AVL树的平衡性就有可能发生改变,为了控制整棵树的高度,我们需要通过一系列变换(重平衡)来保证它仍满足AVL的平衡条件。我们把需要重新平衡的节点叫做 ,由于任意节点最多有两个儿子,因此高度不平衡时,的两颗子树高度差2。考虑一下产生不平衡会有几种情况,稍加思索就会明白——四种情况的插入

  1. ⍺->left->left
  2. ⍺->left->right
  3. ⍺->right->left
  4. ⍺->right->right

情形142分别是关于的镜像对称,从理论上来讲只有两种情况,当然,从编程角度还是四种情况。

 

先说一些约定

struct AvlNode;
typedef struct AvlNode *Position;
typedef struct AvlNode *AvlTree;

struct AvlNode
{
    int value;
    AvlTree  lc;
    AvlTree  rc;
    int      Height;
};

int max(int a,int b){ return a>b?a:b;}

 

 

下面先从思路角度予以说明,一段思路说清后立即给出代码实现,趁热打铁,就易于接受了。

 

外侧情形-单旋转

第一种情况是发生在外侧(左-左or右-右),情形14这需要一次单旋转来完成。

 

先说“右-右”的情况,这种旋转也叫zag

这里两个灰色的方块是可能插入的节点,虚线连接表示只能取一处,g是可能发生失衡的最深的节点(因为往上的祖先也有可能发生失衡),而g就是当前发生失衡最近的节点。我们要围绕着g做一次”右-右“旋转。先说一下宏观的思路:为了使树恢复平衡,我们把p的右子树整体上移一层,并把T0下移一层,不过这样一来,实际上超出了AVL的特性要求,为此我们重新安排节点以形成一颗等价的树,如下所示:

 

抽象地形容就是:把树形象地看成是柔软灵活的,抓住节点p,闭上你的双眼使劲摇动它,在重力作用下p就成了新的根。BST的性质告诉我们,在原树中g<p,于是在新树中g变成了

p的左孩子,T0和p的右子树的各自隶属关系仍然不变。子树T1包含原树中介于g和p之间的的那些节点(因为原树中g<T1<p),可以将它放在新树中g的右孩子的位置上,这样所有对元素大小的要求都能得到满足了。

 

怎么做呢?首先用一个临时指针指向p

 

 

然后我们让T1成为g的右孩子,为此要这样调整:

 

 

接下来我们令g成为p的左孩子:

 

 

接下来我们要让局部子树的根由g变化为p,然后临时指针退休。 

 

 

如此一来就完成了“右-右”的单旋转,整理一下就能看得更清了:

 

 

让我们把这个思路兑现为代码

/*
只有在 g 存在右孩子的情况下才被调用,在g和他的右孩子之间进行旋转操作。
最后要记得实时更新高度,返回当前的新树根
*/
static Position
SingleRotateWithRight( Position g )
{
    
    Position temp;
    
    temp = g->rc;
    g->rc = temp->lc;
    temp->lc = g;
    
    g->Height = max( Height( g->lc ), Height( g->rc ) ) + 1;
    temp->Height = max( Height( temp->rc ), g->Height ) + 1;
    
    return temp;  /* New root */
}
View Code

 

如果在此前g以上的祖先还有发生失衡,在这个局部重平衡之后,上面的各个节点也能一并恢复平衡。因为在这里除了平衡因子外,局部子树还有一个指标:高度。留意一下我们设置的三条基准线,在插入新节点之前,原树的高度以中线为基准,对照重新平衡后的树,它的高度又回到了中间的基准线上。那这又意味着什么呢?这意义十分重大,意味着他所有祖先在计算平衡因子时所得结果,也将与插入节点前完全一样,换而言之,上面的节点也都恢复平衡,那么全树都恢复了平衡。而我们只做了一次“右-右”旋转,只涉及常数个节点,时间消耗O(1),这再好不过了。

 

再说“左-左旋转,也叫zig,比如对于这个局部

 

 

先用一个临时指针指向v

 

 

然后让T2成为p的左孩子 

 

然后让p成为v的右孩子 

 

最后把局部子树的根由p变更为v,临时指针下岗

 

至此“左-左”旋转宣告完成,兑换为代码:

 

//仅当p存在左孩子时调用这个函数,更新高度并返回新的根

static Position
SingleRotateWithLeft( Position p )
{
    Position temp;
    
    temp = p->lc;
    p->lc = temp->rc;
    temp->rc = p;
    
    p->Height = max( Height( p->lc ), Height( p->rc ) ) + 1;
    temp->Height = max( Height( temp->lc ), p->Height ) + 1;
    
    return temp;  /* New root */
}
View Code

 

上面的算法有一个问题,就是解决的情况都是父子节点在朝向上是一致的,如果朝向不一致呢?单旋转就有心无力了,经过单旋转并不会降低它的深度,就需要引入第二种情况了:

 

内侧情形-双旋转

第二种情况是发生在内侧(左-右or右-左),这需要一次双旋转来完成,其实就是两个单旋转的组合,往往是方向相反的一组单旋转协同工作

 

先说右-左的情况如下

 

 

我们要抽丝剥茧地做重平衡操作,先看p-v这个局部,都朝向左边,所以首先的思路是对p执行一次顺时针的左-左的单旋转,就变成了这样: 

 

 

到这里,g,v,p三个节点就朝向一致了,那么显然,接下来我们要针对g做一次逆时针的zag旋转,和之前说的过程完全一样:

T1成为g的右孩子

 

 

g成为v的左孩子(感觉这只蜘蛛要扑过来了2333

 

 

这样就完成了局部的重平衡,当然,这里再把细节展示出来是为了方便深入理解,实际写代码的时候直接调用对应单旋转操作,把g传进去就行了。为了清晰看出效果,做一下整理:

 

 

的确已经恢复平衡,以上可能失衡的祖先也会一并回复平衡。

 

// This function can be called only if g has a right 
// child and g's right child has a left child 
// Do the right-left double rotation 
// Update heights, then return new root 

static Position
DoubleRotateWithRight( Position g )
{
    // Rotate between p and v, p means g->rc
    g->rc = SingleRotateWithLeft( g->rc );
    
    // Rotate between g and p
    return SingleRotateWithRight( g );
}
View Code

 

 

下面再说左-右旋转的情况

 

 

为了重新平衡,就不能让k3继续是根了,不然高度永远降不下来。那么唯一的选择就是让k2作为新的根,如此一来根据BST的性质,我们必须把k1放在左孩子的位置上k3放在右孩子的位置上。具体的做法是对k1,k2这个局部,由于父子朝向都向右,直觉也告诉我们要做一次右-右旋转:B成为k1的右孩子k1这颗子树成为k2的左孩子。最后把k3的左孩子这颗子树的根变更为k2

 

具体细微过程前面单旋转的时候说过了,这里就给出拆掉脚手架后的中间成品

 

 

 

稍微整理一下就更明了了,把k2的高度提上去

 

 

到这一步之后,我们再把k3-k2这个局部,由于此时朝向都为左,那么顺理成章做一次左-左旋转:C成为k3的左孩

k3这颗子树成为k2的右孩子。至此,左-右旋转完成,全树的高度得到了控制。

 

// This function can be called only if K3 has a left
// child and K3's left child has a right child
// Do the left-right double rotation
// Update heights, then return new root

static Position
DoubleRotateWithLeft( Position K3 )
{
    // Rotate between K1 and K2 
    K3->lc = SingleRotateWithRight( K3->lc );
    
    // Rotate between K3 and K2 
    return SingleRotateWithLeft( K3 );
}
View Code

 

 

这四种旋转策略已经覆盖了插入操作失衡的所有情况,下面给出总的插入操作,汇总了这四种情况。

AvlTree
Insert( int X, AvlTree T )
{
    if( !T ){//这里是实质的插入部分,无中生有
        //创建并返回一个单节点树
        T = (Position)malloc( sizeof( struct AvlNode ) );
        if( !T ) printf("Fatal Error: Out Of Space!\n");//错误检测
        else{
            T->value = X;
            T->Height = 0;
            T->lc = T->rc = nullptr;
        }
    }
    
    //还未走到应插入的地点时
    else
        if( X < T->value ) //遵循BST的规则,new value < root value,往左走
        {
            T->lc = Insert( X, T->lc );//此时插入完成后,t指向被插入节点的父亲
            if( Height( T->lc ) - Height( T->rc ) == 2 )
                 //如果新插入节点后lc比rc深2层,那么就是情形1,2
                if( X < T->lc->value )//如果是这样,根据BST规则,是左-左
                    T = SingleRotateWithLeft( T );
                else //否则是左-右
                    T = DoubleRotateWithLeft( T );
         /*
          我们需要根据情况去采取不同的旋转策略,使其恢复平衡
          单旋转调整了情形1:发生在外侧,对a的lc->lc插入
          双旋转调整了情形2:发生在内侧,对a的lc->rc插入
          */

        }
        else
            if( X > T->value )
            {
                T->rc = Insert( X, T->rc );
                //遵循BST的规则,new value > root value,往右走
                
                if( Height( T->rc ) - Height( T->lc ) == 2 )
                    //如果新插入节点后右子树更高,那么就是情形3,4
                    if( X > T->rc->value )  //如果是这样,根据BST规则,是右-右
                        T = SingleRotateWithRight( T );
                    else //否则是右-左
                        T = DoubleRotateWithRight( T );
                /*
                 这个分支里
                 单旋转调整了情形3:发生在外侧,对a的rc->rc插入
                 双旋转调整了情形4:发生在内侧,对a的rc->lc插入
                */
            }
    
    /* Else X is in the tree already; we'll do nothing */
    
    T->Height = max( Height( T->lc ), Height( T->rc ) ) + 1;
    return T;
}

 

最后可以做一个很直观的比较:分别构建大数据量的BST和AVLT,比较他们的高度,就可以明显看出平衡操作对于高度的有效控制了,给一个完整版本的实现吧,可以对比下和普通BST的层数差距。 

 

 

  1 #include "avltree.h"  //这里只给出.c的部分,头文件就是前文的类型声明+各种函数签名
  2 #include <stdlib.h>
  3 #include <stdio.h>
  4 #include <time.h>
  5 
  6 
  7 int max(int a,int b){return a>b?a:b;}
  8 
  9 
 10 
 11 int updateH(AvlTree x){
 12     return x->Height = 1 + max ( Height ( x->lc ), Height ( x->rc ) );
 13 }
 14 
 15 
 16 AvlTree
 17 MakeEmpty( AvlTree T )
 18 {
 19     if( T != NULL )
 20     {
 21         MakeEmpty( T->lc );
 22         MakeEmpty( T->rc );
 23         free( T );
 24     }
 25     return NULL;
 26 }
 27 
 28 
 29 
 30 
 31 void Preorder(Position root);
 32 
 33 int main() {
 34     srand(time(NULL));
 35     AvlTree a=nullptr;
 36     int nodeCnt,del;
 37     printf("Please input how many nodes in the avl tree: ");
 38     scanf("%d",&nodeCnt);
 39     for(int i=0;i<nodeCnt;i++) a=Insert(rand()%(nodeCnt<<1), a);
 40     Preorder(a);
 41     printf("\n\nThe height of avlt with %d nodes is : %d\n",nodeCnt,Height(a));
 42 //    scanf("%d",&del);
 43 //    DeleteInAVL(del, a);
 44 //    Preorder(a);
 45 }
 46 
 47 Position
 48 Find( int X, AvlTree T )
 49 {
 50     if( !T )
 51         return NULL;
 52     if( X < T->value ){
 53         return Find( X, T->lc );
 54     }
 55     else
 56         if( X > T->value )
 57             return Find( X, T->rc );
 58         else
 59             return T;
 60 }
 61 
 62 Position
 63 FindMin( AvlTree T )
 64 {
 65     if( !T )
 66         return NULL;
 67     else
 68         if( T->lc == NULL )
 69             return T;
 70         else
 71             return FindMin( T->lc );
 72 }
 73 
 74 Position
 75 FindMax( AvlTree T )
 76 {
 77     if( T != NULL )
 78         while( T->rc != NULL )
 79             T = T->rc;
 80     
 81     return T;
 82 }
 83 
 84 
 85 // This function can be called only if g has a left child
 86 // Perform a rotate between a node (g) and its left child
 87 // Update heights, then return new root
 88 
 89 static Position
 90 SingleRotateWithLeft( Position p )   //左-左的情况
 91 {
 92     Position temp;
 93     
 94     temp = p->lc;
 95     p->lc = temp->rc;
 96 
 97     temp->rc = p; 
 98     
 99     p->Height = max( Height( p->lc ), Height( p->rc ) ) + 1;
100     temp->Height = max( Height( temp->lc ), p->Height ) + 1;
101     
102     return temp;  /* New root */
103 }
104 
105 
106 // This function can be called only if g has a right child
107 // Perform a rotate between a node (g) and its right child
108 // Update heights, then return new root
109 
110 static Position
111 SingleRotateWithRight( Position g )   //右-右的情况
112 {
113     Position temp;
114     
115     temp = g->rc;
116     g->rc = temp->lc;
117 
118     temp->lc = g;     
119     
120     g->Height = max( Height( g->lc ), Height( g->rc ) ) + 1;
121     temp->Height = max( Height( temp->rc ), g->Height ) + 1;
122     
123     return temp;  /* New root */
124 }
125 
126 
127 // This function can be called only if K3 has a left
128 // child and K3's left child has a right child
129 // Do the left-right double rotation
130 // Update heights, then return new root
131 
132 static Position
133 DoubleRotateWithLeft( Position K3 )   //左-右的情况
134 {
135     /* Rotate between K1 and K2 */
136     K3->lc = SingleRotateWithRight( K3->lc );
137 
138     
139     /* Rotate between K3 and K2 */
140     return SingleRotateWithLeft( K3 );
141 }
142 
143 // This function can be called only if g has a right
144 // child and g's right child has a left child
145 // Do the right-left double rotation
146 // Update heights, then return new root
147 
148 static Position
149 DoubleRotateWithRight( Position g )   //右-左的情况
150 {
151     // Rotate between p and v, p means g->rc
152     g->rc= SingleRotateWithLeft( g->rc );
153 154     
155     // Rotate between g and p
156     return SingleRotateWithRight( g );
157 }
158 
159 
160 AvlTree
161 Insert( int X, AvlTree T )
162 {
163     Position p;// it means p on the "new node"
164     if( !T ){//这里是实质的插入部分,无中生有
165         //创建并返回一个单节点树
166         T = (Position)malloc( sizeof( struct AvlNode ) );
167         if( !T ) printf("Fatal Error: Out Of Space!\n");//错误检测
168         else{
169             T->value = X;
170             T->Height = 0;
171             T->lc = T->rc = nullptr;
172         }
173     }
174     
175     //还未走到应插入的地点时
176     else
177         if( X < T->value ) //遵循BST的规则,new value < root value,往左走
178         {
179             T->lc = Insert( X, T->lc );
180             //此时插入完成后,T指向被插入节点的父亲,新生节点作为T的左孩子而存在。
181             
182             if( Height(T->lc)-Height(T->rc) == 2 )
183                 //如果新插入节点后lc比rc深2层,那么就是情形1,2
184                 if( X < T->lc->value )//如果是这样,根据BST规则,是左-左
185                     T = SingleRotateWithLeft( T );
186                 else //否则是左-右
187                     T = DoubleRotateWithLeft( T );
188             /*
189              我们需要根据情况去采取不同的旋转策略,使其恢复平衡
190              单旋转调整了情形1:发生在外侧,对a的lc->lc插入
191              双旋转调整了情形2:发生在内侧,对a的lc->rc插入
192              */
193             
194             
195         }
196         else
197             if( X > T->value ) //遵循BST的规则,new value > root value,往右走
198             {
199                 T->rc = Insert( X, T->rc );
200                 //此时插入完成后,T指向被插入节点的父亲,新生节点作为T的右孩子而存在。
201                 if( Height(T->rc)-Height(T->lc) == 2 )
202                     //如果新插入节点后右子树更高,那么就是情形3,4
203                     if( X > T->rc->value )  //如果是这样,根据BST规则,是右-右
204                         T = SingleRotateWithRight( T );
205                     else //否则是右-左
206                         T = DoubleRotateWithRight( T );
207                 /*
208                  这个分支里
209                  单旋转调整了情形3:发生在外侧,对a的rc->rc插入
210                  双旋转调整了情形4:发生在内侧,对a的rc->lc插入
211                  */
212                 
213                 //
214             }
215     
216     /* Else X is in the tree already; we'll do nothing */
217     
218     updateH(T);
219     return T;
220 }
221 
222 
223 int
224 Retrieve( Position P )
225 {
226     return P->value;
227 }
228 
229 
230 void Preorder(Position root){
231     if (root) {
232         printf("%d ",root->value);
233         Preorder(root->lc);
234         Preorder(root->rc);
235     }
236 }

 

祝食用愉快2333

 

ps.转载请注明文章来源,否则会追加法律责任。

posted @ 2018-07-20 14:36  仪式黑刃  阅读(829)  评论(1编辑  收藏  举报