平衡二叉樹——如何實現不平衡二叉樹到平衡二叉樹
平衡二叉樹定義(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;
}