1. 程式人生 > >平衡二叉樹——如何實現不平衡二叉樹到平衡二叉樹

平衡二叉樹——如何實現不平衡二叉樹到平衡二叉樹

平衡二叉樹定義(AVL):它或者是一顆空樹,或者具有以下性質的二叉樹:它的左子樹和右子樹的深度之差的絕對值不超過1,且它的左子樹和右子樹都是一顆平衡二叉樹。

平衡因子(bf):結點的左子樹的深度減去右子樹的深度,那麼顯然-1<=bf<=1;

很顯然,平衡二叉樹是在二叉排序樹(BST)上引入的,就是為了解決二叉排序樹的不平衡性導致時間複雜度大大下降,那麼AVL就保持住了(BST)的最好時間複雜度O(logn),所以每次的插入和刪除都要確保二叉樹的平衡,那麼怎麼保持平衡呢?

我努力看了看資料結構上的講解,但是看的只暈+_+!我對他的講解很無語,他所謂的“旋轉”操作講的不明不白,看的我氣的蛋疼!你說你旋轉,你得說清是如何旋轉?以那個結點為中心,那些或者說那個結點轉了,那些結點不動。你就在哪裡旋轉來旋轉去的,誰知道你是咋轉的,你在哪慢慢轉吧!哥轉不過你,另找高明!於是在網上找啊找,只為想搞明白是到底怎麼轉的!

點選開啟連結讓我對“旋轉”有所領悟,表示感謝!

插入時:

那究竟是如何“轉”的呢?

首先必須明白一個核心操作,不讓它叫“旋轉”!而叫——>“兩個結點的變換”

如圖:

就拿第一個來說->點100和101的變換:

點101佔據點100的位置,點100換到了101的對面的位置,其他點的相對位置不變。

我們可以更直觀的理解為:把101結點“上提”一下!

分析:101>100,所以100可以作為101的左孩子;

也就是在二叉排序樹中,兩個結點這樣的變換操作是可行的,是符合二叉排序樹的性質。

不僅這個簡單的圖,任何複雜的二叉排序樹都可以,你可以試試,也許你會說如果點101左邊有孩子怎麼辦?彆著急~,當然有辦法!

下邊正式說這個圖的四種不平衡情況(插入時)及操作:

首先還需要明白一個概念->最小不平衡子樹的根結點:也就是當你進行插入操作時,找到該需要插入結點的位置並插入後,從該結點起向上尋找(回溯),第一個不平衡的結點即平衡因子bf變為-2或2。

為什麼要引入這個最小不平衡根結點的概念,因為在插入時,對該子樹進行保持平衡操作後,其它的結點的平衡因子不會變,也就是整棵樹又恢復平衡了。為什麼呢?

你想想不平衡點的bf一定是-2或2吧,經過平衡操作後,他會把一邊子樹的一個結點分給另一邊的子樹,也就是一邊的深度分給另一邊,這樣就平衡了!

比如,插入前:左邊是深度1,右邊深度是0;插入後左邊深度是2,右邊深度是0,經過平衡後左邊深度是1,右邊深度是1;

那麼你說插入前和插入後該根結點所領導的子樹的深度變沒??仍是1,顯然沒變!那麼就仍保持了這棵樹的平衡了!

下面即四種情況分別為:左左、右右、左右、右左,每種情況又有兩個圖:①、②,①是該情況的最簡單的圖形,②是該情況的一般的圖形;

設x為最小不平衡子樹的根結點,y為剛插入的點

左左:

即在x的左孩子a的左孩子c上插入一個結點y(該結點也可以是c,如圖①),即y可以是c,也可以是c的左孩子(如圖②),也可以是c的右孩子(不在畫出)

                                  

圖①就不用說了,結點x和結點a變換,則樹平衡了;那麼圖②就是樹中的一般情況了a結點有右孩子d,那要進行x和a變換,那麼a的右孩子放哪啊?

很簡單,如圖放在x的左孩子上;分析:x>d,d>a,所以d可作為x的左孩子,且可作為a的右孩子中的孩子。下邊這樣的類似情況不再一一分析,自己分析分析~

實現:找到根結點x,與它的左孩子a進行交換即可使二叉樹樹再次平衡;

右右:

即在x的右孩子a的右孩子c上插入一個結點y(該結點也可以是c,如圖①),即y可以是c,也可以是c的右孩子(如圖②),也可以是c的左孩子(不在畫出)

實現:找到根結點x,與它的右孩子a進行交換即可使二叉樹樹再次平衡;

左右:

即在x的左孩子a的右孩子c上插入一個結點y(該結點也可以是c,如圖①),即y可以是c,也可以是c的右孩子(如圖②),也可以是c的左孩子(不在畫出)

這個左右和下邊的右左,稍微複雜了點,需要進行兩次交換,才能達到平衡,注意這時y是c的右孩子,最終y作為x的左孩子;若y是c的左孩子,最終y作為a

的右孩子,畫圖分析一下~~下邊類似,不再敖述。

實現:找到根結點x,讓x的左孩子a與x的左孩子a的右孩子c進行交換,然後再讓x與x此時的左孩子c進行交換,最終達到平衡;

右左:

即在x的右孩子a的左孩子c上插入一個結點y(該結點也可以是c,如圖①),即y可以是c,也可以是c的右孩子(如圖②),也可以是c的左孩子(不在畫出)

實現:找到根結點x,讓x的右孩子a與x的右孩子a的左孩子c進行交換,然後再讓x與x此時的右孩子c進行交換,最終達到平衡;

上邊的四種情況包含了所有插入時導致不平衡的情況,上面給出的僅僅是一棵大樹中的最小不平衡子樹,一定要想清楚,別迷了!

另外一定要注意這個交換操作,比如a與b交換(a在上,b在下),b一定要佔據a的位置!什麼意思?就是b要放在(覆蓋)儲存a的那塊記憶體上,

再通俗點說,若a是x的左孩子,則交換後b要做x的左孩子;這就是所謂的b佔據a的位置!

那麼如何找到最小不平衡子樹的根結點x,並判斷出它是屬於那種情況的?

插入一個結點時,我們首先找到需要插入的位置,並插入;資料結構上用的是遞迴,不要說遞迴太浪費時空,你想想一個含2^31個結點的平衡二叉樹的深度大約是31吧,它遞迴再多也不就是31層!而且遞迴程式碼短小、精悍、富含藝術之美!所以我認為對於這個平衡二叉樹,用遞迴很合適!

顯然插入之後就要檢查是否出現不平衡的結點,那麼如何檢查?

我們知道,你插入的時候用的是遞迴,一條線找到要插的位置,並插入;那麼誰的平衡因子的有可能變呢?

不難想象,只有該條線上的結點的平衡因子有可能改變!那麼我們在回溯的時候不就可以找到第一個不平衡的子樹的結點?!

可是我們如何判斷該結點的平衡因子是否應該改變,顯然要看它被插入結點的一邊的深度是否增加;

如何看它被插入結點的一邊的深度是否增加?

如上圖,如何看x的右孩子a(即被插入結點的一邊)的深度增加?我們知道在a的右孩子上插入了結點y那麼a的bf是一定要減1

那麼x結點的bf?可根據a的bf決定是否改變!

若a:bf=-1或1,那麼a之前一定為0,表示a的深度增加了,那麼x的bf可根據a是x哪一邊的孩子決定+1或-1;

若a:bf=0,那麼a之前一定為-1或1,表示a的深度每增加,那麼不僅x的bf就不用變,該條線上的所有結點的bf都不用變,直接返回即可;

當然了,那麼找到最小不平衡子樹的根結點x了,如何判斷它屬於哪種不平衡呢?

①根據上邊的幾種情況,我們需要知道兩個方向,在回溯時可以記錄一下該結點到下一個結點的方向0:左、1:右為第二個方向,傳遞給上一層中,那麼上層中的方向就是一個方向,有了這兩個方向就能確定是哪種不平衡了。

還就上邊的圖說吧~可以定義一個全域性變數secdirection(第二個方向),也可在遞迴中定義一個區域性變數,返回給上一層。在回溯到a中時,secdirection=1,到x的時候

x->a的方向也為1,定義firdirection=1;而這時x:bf=-2;那麼就找到了最小不平衡子樹的根結點x,又知道了兩個方向,那麼進行相應的平衡操作不就行了。

②其實我程式碼中的就是按照①寫的,可是剛又想了,其實不用用個變數記錄第二個方向,可以根據a的bf確定它的第二個方向,a:bf=-1說明右孩子的深度增加,y加到右孩子上;

a:bf=1;說明左孩子的深度增加,y加到左孩子上;

好了,找到了最小不平衡子樹的根結點x了,也知道了那種不平衡,呼叫keepbalance(...)就使樹平衡了,可是某些結點的平衡因子在變換是變了~~咋辦?

我就是一種一種情況推出來的,不知道別人怎麼變的,每一種情況都有固定的幾個點變了,變成了一個固定的值!誰有更好的辦法,請多多指教!

下邊一一列出(插入操作中)變換後bf改變的結點及值:

左左:前a->bf=1 後 x->bf=0,a->bf=0;右右:前a->bf=-1 後x->bf=0,a->bf=0;顯然左左與右右的x與a變換後的bf都為0;

左右、右左中結點bf的改變要根據之前c的bf!

左右:若c->bf=1,則x->bf=-1,a->bf=0,c->bf=0;若c->bf=-1,則x->bf=0,a->bf=1,c->bf=0;若c->bf=0,則x->bf=0,a->bf=0,c->bf=0;

右左:若c->bf=1,則x->bf=0,a->bf=-1,c->bf=0;若c->bf=-1,則x->bf=1,a->bf=0,c->bf=0;若c->bf=0,則x->bf=0,a->bf=0,c->bf=0;

可以發現,當左右、右左的c->bf相同時x與a的bf剛好取相反的值。

好了,到現在插入一個結點的二叉樹終於平衡了,相應的平衡因子也修改了!插入算是完成了!!

刪除時:

刪除類似插入的操作,蛋又不同,刪除會有一些特殊情況需要特殊處理,當然核心操作“保持平衡”是不變的!

刪除時少一個結點,也就是該結點所在的子樹深度可能會減小,而插入時多一個結點,該結點所在的子樹深度可能會增加,

所以遞迴刪除一個結點時,回溯時找到最小不平衡子樹的根結點時,要向相反的方向去找屬於哪種情況;

如圖:

y為要刪除的結點;

圖①:y結點刪除後,回溯到x結點x:bf=-1變為x:bf=-2;則需從相反方向即從x的右孩子的方向向下檢查屬於哪種情況,顯然第一個方向為1:右;

第二個方向看a:bf的值——若為1時,那就相當於插入時‘右左’的情況;若為-1時,那就相當於插入時‘左左’的情況;可現在a:bf既不是1也不是-1

而是0,這就是刪除的特殊情況了!我們不妨試試對他進行類似於插入時的‘右右’操作,看怎麼樣~    如上圖,經過變換後該子樹平衡了!但是因子的

修改就跟插入時的‘右右’不一樣了!此時變為:x:bf=-1,a:bf=1;所以我們不妨就把a:bf=0也歸納為刪除的‘右右’或‘左左’(如圖②,不再敖述)操作;

那麼刪除時因子的改變需在插入時因子的改變中新增上:

左左:前a:bf=0 後x:bf=1,a:bf=-1; 右右:前a:bf=0 後x:bf=-1,a:bf=1;其他不變!

插入時結束結點平衡因子的修改,直接返回(也就是該樹已經平衡了):

回溯時發現兒子結點的平衡因子為0(當發現不平衡結點,並進行平衡操作後,平衡後根結點的bf一定為0,也結束了)

但是刪除時結束結點平衡因子的修改,直接返回,就與插入時不一樣了:回溯時發現兒子結點的平衡因子為-1或1!

再刪除操作中,平衡一個子樹後,該子樹的深度不一定不變,而只有上邊那種特殊情況該子樹的深度不變,其他都會變!

可以想象,其實是很簡單的道理:除了特殊情況其他都與插入的情況一模一樣,說白了就是把深度大的子樹(根結點的其中一個)向深度小子樹貢獻一個深度,

那麼這樣一來,該子樹(對於根結點所領導的樹)的深度是不是比原來的小1了?!所以要繼續向上一個一個進行檢索,直到根結點為止!

好了,到這裡刪除也算是說完了,可以貼程式碼了吧~

#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 typedef int Elemtype;
 
 typedef struct Balanced_Binary_Tree
 {
     Elemtype e;
     int bf;
     struct Balanced_Binary_Tree *child[2];
 }*AVL;
 
 ///------------簡單的位操作-------------------
 void setbit(char *i,char val,char pos)
 {
     if(pos==1)
         (*i)=(*i)|1;
     else
     {
         if(val==1)    (*i)=(*i)|2;
         else    (*i)=(*i)&1;
     }
 }
 char getbit(char i,char pos)
 {
     ///除錯時,發現這裡能返回2///
 //    return (i&pos); 出錯的地方
     return (i&pos)&&1;
     /////////////////////////////
 }
 ///--------------------------------------------
 
 ///-----------生成一個結點---------------------
 AVL createnode(Elemtype e)
 {
     AVL node=NULL;
 
     node=(AVL)malloc(sizeof(struct Balanced_Binary_Tree));
     node->e=e;    node->bf=0;
     node->child[0]=node->child[1]=NULL;
     
     return node;
 }
 ///---------------------------------------------
 
 ///★★★★★★★★AVL的核心操作★★★★★★★★★★★★
 ///★★★★★★★★★保持平衡★★★★★★★★★★★★★★
 
 //改變因子函式
 void setfactor(AVL f,int button)
 {
     char fir=button/10,sec=button%10;
     AVL s=f->child[fir],ss=s->child[sec];
     char choice=ss->bf;
     int a=1,b=0;
 
     //////////除錯時發現,刪除時的特殊情況/////////////
 /////插入時,不會有這種情況,若button=0,則s->bf=1//
 /////若button=11,則s->bf=-1;然而刪除時,卻會出現/
 /////button=0或者button=11時 s->bf=0!!!!!!!////////
 /////那麼這種特殊情況,平衡後所得的因子就跟一般的//
 /////不一樣了!!!如下///////////////////////////////
     if(button==0 && s->bf==0)    f->bf=1,s->bf=-1;
     else if(button==11 && s->bf==0)    f->bf=-1,s->bf=1;
     ///////////////////////////////////////////////////
     else if(button==0 || button==11)
     {
         f->bf=0;
         s->bf=0;
     }
     else
     {
         /////寫部落格時,發現這裡有問題///////////////////
     //    if(button==1)    choice=-choice;
         /////但是為什麼我測試的時候怎麼都對?!///////////
 /////經再次測試,上邊確實錯了!!!////////////////
 /////改為下邊應該就對了吧///////////////////////
         if(button==1)    {a^=b,b^=a,a^=b;}
         ////////////////////////////////////////////////
 
         if(choice==-1)    f->bf=a,s->bf=b;
         else if(choice==0)    f->bf=s->bf=0;
         else    f->bf=-b,s->bf=-a;
         
         ss->bf=0;
     }
 }
 //兩節點變換函式
 void conversion(AVL *T,char direction)
 {
     AVL f=*T,s=f->child[direction];
 
     f->child[direction]=s->child[!direction];
     s->child[!direction]=f;
     *T=s;
 }
 //保持平衡函式
 void keepbalance(AVL *T,char fir,char sec)
 {
     AVL *s=&((*T)->child[fir]);
     int button=fir*10+sec;
 
     if(button==0 || button==11)
     {
         setfactor((*T),button);
         conversion(T,fir);
     }
     else
     {
         setfactor((*T),button);
         conversion(s,sec);
         conversion(T,fir);
     }
 }
 ///★★★★★★★★★★★★★★★★★★★★★★★★★★
 
 ///------------插入時的選向操作-------------------
 void selectforInsert(AVL *T,char *info,int direction)
 {
     AVL cur=*T;
     char firdirection,secdirection;
 
     if(direction==0)    (cur->bf)++;
     else    (cur->bf)--;
 
     if(cur->bf==0)    setbit(info,1,1);
     else if(cur->bf==-1 || cur->bf==1)    setbit(info,direction,2);
     else
     {        
         firdirection=direction;
         secdirection=getbit(*info,2);
         keepbalance(T,firdirection,secdirection);
         setbit(info,1,1);
     }
 }
 //----------------------------------------------
 
 //*************插入操作************************//
 char InsertAVL(AVL *T,Elemtype e)
 {                                //可用於查詢
     char info;
     
     if(!(*T))
     {
         (*T)=createnode(e);
         return 0;
     }
     else if((*T)->e==e)        return -1;
     else if((*T)->e>e)//左
     {
         info=InsertAVL(&((*T)->child[0]),e);
 
         if(getbit(info,1))    return info;
         
         selectforInsert(T,&info,0);
     }
     else              //右
     {
         info=InsertAVL(&((*T)->child[1]),e);
 
         if(getbit(info,1))    return info;
 
         selectforInsert(T,&info,1);
     }
     return info;
 }
 //*********************************************//
 
 //-------------刪除時的選向操作--------------------
 void selectforDelete(AVL *T,char *info,char direction)
 {
     AVL cur=(*T);
     char firdirection,secdirection;
 
     if(direction==0)    (cur->bf)--;
     else    (cur->bf)++;
     
     if(cur->bf==0)    *info=0;
     else if(cur->bf==-1 || cur->bf==1)    *info=1;
     else
     {
         firdirection=!direction;
         ///除錯時,發現這裡少寫了一個等號////////////////////
 //        if(cur->child[firdirection]->bf=1)    secdirection=0;草,真帥氣!原來1==a這樣寫確實有必要!
         if(1==cur->child[firdirection]->bf)    secdirection=0;
         /////////////////////////////////////////////////////
         else    secdirection=1;
         keepbalance(T,firdirection,secdirection);
 
         /////////////////////////////////////////////////////////////////////////////////////////
 ///除錯時,發現經過子樹平衡操作後,*info不一定都是0,就是那個特殊情況,在setfactor中/////
 ///的那種特殊情況時,這裡*info應改為1! 所以程式碼改如下://////////////////////////////////
 /////////////////////////////////////////////////////////////////////////////////////////
     //    *info=1; 寫程式碼時:這跟插入可不一樣啊...該子樹平衡了,它父節點的因子比變!
 //    *info=0;//因此,這還沒完還要是0!! ............啊……這裡不一定是0! 
 ////還是那個特殊情況搞的鬼!//
         if(cur->bf==0)    *info=0;
         else    *info=1;
         /////////////////////////////////////////////////////////////////////////////////////////
     }
 }
 //------------------------------------------------
 
 //-------------變向遞迴--輔助刪點-----------------
 char find(AVL *gogal,AVL *p)
 {
     char info;
     AVL tp=NULL;
     
     if(NULL!=(*p)->child[0])
     {
         info=find(gogal,&((*p)->child[0]));
         if(info!=0)    return info;
         selectforDelete(p,&info,0);
     }
     else
     {
         (*gogal)->e=(*p)->e;
         tp=(*p)->child[1];
         free((*p));
         *p=tp;
         info=0;
     }
     return info;
 }
 //------------------------------------------------
 
 //**************刪除操作*************************//
 char DeleteAVL(AVL *T,Elemtype e)
 {
     char info;
     AVL tp=NULL;
 
     if(!(*T))    return -1;//原if(!T)    return -1;於2011年11月29日8:59:15修改
     else if((*T)->e==e)
     {
         if(NULL!=(*T)->child[1])
         {
             info=find(T,&((*T)->child[1]));
             if(info!=0)    return info;
             selectforDelete(T,&info,1);
         }
         else
         {
             //////////////除錯時,發現這樣寫不對!!!///////////////////////////////////////
         //    (*T)=(p=(*T)->child[0])-(free((*T)),0);//Just save a variable! 這裡出問題
 //    (*T)=p-(free((*T)),0); 可以
 //    (*T)=(p=((*T)->child[0]))+(free((*T)),0); 不可以
             tp=((*T)->child[0]);
             free((*T));
             *T=tp;
             //除錯時,發現這裡漏了給info賦值
             info=0;
             ///////////////////////////////////////////////////////////////////////////////
         }
     }
     else if((*T)->e>e)
     {
         info=DeleteAVL(&(*T)->child[0],e);
         if(info!=0)    return info;
         selectforDelete(T,&info,0);
     }
     else
     {
         info=DeleteAVL(&(*T)->child[1],e);
         if(info!=0)    return info;
         selectforDelete(T,&info,1);
     }
     return info;
 }
 //************************************************//
 
 
 //*****************JUST FOR TEST************************//
 #define MOVE(x)    (x=(x+1)%1000)
 AVL queue[1000];
 
 void print(AVL p,int i)
 {
     int front,rear,temp,count;
 
     front=rear=-1; count=temp=0;
     queue[MOVE(rear)]=p; count++;
     
     printf("%d\n",i);
     while(front!=rear)
     {
         p=queue[MOVE(front)];    count--;
         
         
         if(p->child[0])    queue[MOVE(rear)]=p->child[0],temp++;
         if(p->child[1])    queue[MOVE(rear)]=p->child[1],temp++;
         
         printf("%d->%d ",p->e,p->bf);
         if(count==0)
         {
             printf("\n");
             count=temp;
             temp=0;
         }    
     }
     printf("\n");
 }
 //**************************************************//
 
 
 int main()
 {
     AVL T=NULL;
     int i,nodenum=0;
 
     freopen("input.txt","w",stdout);
     nodenum=100;
     for(i=0;i<nodenum;i++)
     {
         InsertAVL(&T,i);
     }
     
     print(T,-1);
 
     for(i=0;i<nodenum-1;i++)
     {
         DeleteAVL(&T,i);
         print(T,i);
     }
     
     return 0;
 }