日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

平衡二叉树算法详解

發布時間:2024/1/18 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 平衡二叉树算法详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.


轉自:http://www.cnblogs.com/fornever/archive/2011/11/15/2249492.html

寫的有點兒俗,理論性不是很強,不過還算通俗易懂。總之,謝謝上位大俠的解釋~~~

平衡二叉樹定義(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了?!所以要繼續向上一個一個進行檢索,直到根結點為止!

?

好了,到這里刪除也算是說完了,可以貼代碼了吧~

?

平衡二叉樹的C實現:

View Code #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;}


?

那個位操作函數,使程序好像很復雜似的,完全可以用一個外部結構體替換!只是我不想用外部變量,盡量在函數內完成吧

由于本人比較笨,寫了很長時間~~有什么不正確的,歡迎指正!!!

這些圖費了很大功夫才弄好,本來在編輯框里畫的好好的,可是一發表,那些圖有些就亂了,剛開始我還試著一個一個對照著修改,看最終發現還是不行……最后忽然想到我怎么不在編輯框中截圖呢?最后在編輯框中一個一個截圖一個一個上傳,這樣效果好多了!費這么大功夫只為一點:讓讀者看著舒服,容易理解。

我還想說:開這個博客園以來,也沒多長時間,蛋把我氣的不淺!每次都是搞好程序,準備到這里寫博客時,就是打不開!氣死我了!卡死啦!有時候確實是我網速慢,但有時候網速好了也很難打開啊!你說你還是程序員的家園!看你卡的,我都無語了!網速稍微慢點就打不開,就這浪費了我很多時間!我對博客園很失望!!

朋友們,你們感覺呢?

總結

以上是生活随笔為你收集整理的平衡二叉树算法详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。