數據結構之 平衡二叉樹

平衡二叉樹,是一種二叉排序樹,其中每一個結點的左子樹和右子樹的高度差至多等於1。它是一種高度平衡的二叉排序樹。高度平衡?意思是說,要麼它是一棵空樹,要麼它的左子樹和右子樹都是平衡二叉樹,且左子樹和右子樹的深度之差的絕對值不超過1。html

    將二叉樹上結點的左子樹深度減去右子樹深度的值稱爲平衡因子BF,那麼平衡二叉樹上的全部結點的平衡因子只多是-一、0和1。只要二叉樹上有一個結點的平衡因子的絕對值大於1,則該二叉樹就是不平衡的。 算法

    平衡二叉樹的前提是它是一棵二叉排序樹。 數組

    距離插入結點最近的,且平衡因子的絕對值大於1的結點爲根的子樹,稱爲最小不平衡子樹。以下圖所示,當插入結點37時,距離它最近的平衡因子的絕對值超過1的結點是58。函數


 

一、平衡二叉樹實現原理 學習

    平衡二叉樹構建的基本思想就是在構建二叉排序樹的過程當中,每當插入一個結點時,先檢查是否因插入而破壞了樹的平衡性,如果,則找出最小不平衡子樹。在保持二叉排序樹特性的前提下,調整最小不平衡子樹中各結點之間的連接關係,進行相應的旋轉,使之成爲新的平衡子樹。 下面講解一個平衡二叉樹構建過程的例子。如今又a[10] = {3, 2, 1, 4, 5, 6, 7, 10, 9, 8}須要構建二叉排序樹。在沒有學習平衡二叉樹以前,根據二叉排序樹的特性,一般會將它構建成以下左圖。雖然徹底符合二叉排序樹的定義,可是對這樣高度達到8的二叉樹來講,查找是很是不利的。所以,更加指望構建出以下右圖的樣子,高度爲4的二叉排序樹,這樣才能夠提供高效的查找效率。spa

 

    如今來看看如何將一個數組構成出如上右圖的樹結構。 對於數組a的前兩位3和2,很正常地構建,到了第個數「1」時,發現此時根結點「3」的平衡因子變成了2,此時整棵樹都成了最小不平衡子樹,須要進行調整,以下圖圖1(結點左上角數字爲平衡因子BF值)。由於BF爲正,所以將整個樹進行右旋(順時針),此時結點2成了根結點,3成了2的右孩子,這樣三個結點的BF值均爲0,很是的平衡,以下圖圖2所示。3d

    而後再增長結點4,平衡因子沒有改變,如上圖圖3。增長結點5時,結點3的BF值爲-2,說明要旋轉了。因爲BF是負值,對這棵最小平衡子樹進行左旋(逆時針旋轉),以下圖圖4,此時整個樹又達到了平衡。指針

 

    繼續增長結點6時,發現根結點2的BF值變成了-2,以下圖圖6所示。因此對根結點進行了左旋,注意此時原本結點3是結點3的左孩子,因爲旋轉後須要知足二叉排序樹特性,所以它成告終點2的右孩子,如圖7所示。code

 

    增長結點7,一樣的左旋轉,使得整棵樹達到平衡,以下圖8和9所示。htm

    

    當增長結點10時,結構無變化,如圖10所示。再增長結點9,此時結點7的BF變成了-2,理論上只須要旋轉最小不平衡樹七、九、10便可,可是,若是左旋轉後,結點9變成了10的右孩子,這是不符合二叉排序樹的特性的,此時不能簡單的左旋。如圖11所示。

    仔細觀察圖11,發現根本緣由在於結點7的BF是-2,而結點10的BF是1,也就是說,它們兩個一正一負,符號並不統一,而前面的幾回旋轉,不管左仍是右旋,最小不平衡子樹的根結點與它的子結點符號都是相同的。這就是不能直接旋轉的關鍵。 不統一,不統一就把它們先轉到符號統一再說,因而先對結點9和結點10進行右旋,使得結點10成了9的右子樹,結點9的BF爲-1,此時就與結點7的BF值符號統一了,如圖12所示。

     

    這樣再以結點7爲最小不平衡子樹進行左旋,獲得以下圖13。接着,插入8,狀況與剛纔相似,結點6的BF是-2,而它的右孩子9的BF是1,如圖14,所以首先以9爲根結點,進行右旋,獲得圖15,此時結點6和結點7的符號都是負,再以6爲根結點左旋,最終獲得最後的平衡二叉樹,如圖16所示。

  

    經過這個例子,能夠發現,當最小不平衡樹根結點的平衡因子BF是大於1時,就右旋,小於-1時就左旋,如上例中的結點一、五、六、7的插入等。插入結點後,最小不平衡子樹的BF與它的子樹的BF符號相反時,就須要對結點先進行一次旋轉以使得符號相同後,再反向旋轉一次纔可以完成平衡操做,如上例中結點九、8的插入時。

    下面兩個圖講解了插入時所要作的旋轉操做的例子。《來自:http://www.cnblogs.com/guyan/archive/2012/09/03/2668399.html》



 二、平衡二叉樹算法的實現 

     首先是須要改進二叉排序樹的結點結構,增長一個bf,用來存儲平衡因子。

1
2
3
4
5
6
typedef struct BitNode
{
       int data;
       int bf;
       struct BitNode *lchild, *rchild;
}BitNode, *BiTree;
      而後對於右旋(順時針)操做,代碼以下:

1
2
3
4
5
6
7
8
void R_rotate(BiTree *t)
{
          BiTree s;
          s = (*t)->lchild;                    //s指向t的左子樹根結點
          (*t)->lchild = s->rchild;          //s的右子樹掛接爲t的左子樹
          s->rchild = (*t);
          *p = s;                                //t指向新的根結點
}
      此函數代碼的意思是說,當傳入一個二叉排序樹t,將它的左孩子結點定義爲s,將s的右子樹變成t的左子樹,再將t改成s的右子樹,最後將s替換爲t的根結點。這樣就完成了一次右旋操做。以下圖示,圖中三角形表明子樹,N表明新增的結點。

                                    

 

    上面的例子中新增長告終點N,就是右旋操做。 

    左旋代碼以下所示。

1
2
3
4
5
6
7
8
void L_rotate(BiTree *t)
{
          BiTree s;
          s = (*t)->rchild;                    //s指向t的右子樹根結點
          (*t)->rchild = s->lchild;          //s的左子樹掛接爲t的右子樹
          s->lchild = (*t);
          *p = s;                                //t指向新的根結點
}
      下面看左旋轉平衡(使左邊平衡)的處理代碼。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#define LH +1 /*  左高 */
#define EH 0  /*  等高 */
#define RH -1 /*  右高 */
 
/*  對以指針T所指結點爲根的二叉樹做左平衡旋轉處理 */
/*  本算法結束時,指針T指向新的根結點 */
void LeftBalance(BiTree *T)
{
     BiTree L,Lr;
     L = (*T)->lchild;                                       /*  L指向T的左子樹根結點 */
     switch (L->bf)
     {
         /* 檢查T的左子樹的平衡度,並做相應平衡處理 */
          case LH:                   /* 新結點插入在T的左孩子的左子樹上,要做單右旋處理 */
             (*T)->bf=L->bf=EH;
             R_Rotate(T);
             break ;
          case RH:                   /* 新結點插入在T的左孩子的右子樹上,要做雙旋處理 */
             Lr=L->rchild;                     /* Lr指向T的左孩子的右子樹根 */
             switch (Lr->bf)
             {   /* 修改T及其左孩子的平衡因子 */
                 case LH: (*T)->bf=RH;
                          L->bf=EH;
                          break ;
                 case EH: (*T)->bf=L->bf=EH;
                          break ;
                 case RH: (*T)->bf=EH;
                          L->bf=LH;
                          break ;
             }
             Lr->bf=EH;
             L_Rotate(&(*T)->lchild);          /* 對T的左子樹做左旋平衡處理 */
             R_Rotate(T);             /* 對T做右旋平衡處理 */
     }
}
   

    首先,定義三個常數變量,分別代碼一、0、-1。

    (1)函數被調用,傳入一個需調整平衡型的子樹T。因爲LeftBalance函數被調用時,實際上是已經確認當前子樹是不平衡的狀態,且左子樹的高度大於右子樹的高度。換句話說,此時T的根結點應該是平衡因子BF的值大於1的數。

    (2)將T的左孩子賦值給L。

    (3)而後是分支判斷。

    (4)當L的平衡因子爲LH,即爲1時,代表它與根結點的BF值符號相同,所以,將它們的BF值都改成0,並進行右旋(順時針)操做,操做方式如圖所示。

    (5)當L的平衡因子爲RH時,即爲-1時,代表它與根結點的BF值符號相反,此時須要作雙旋操做。針對L的右孩子的BF做判斷,修改結點T和L的BF值。將當前的Lr的BF改成0。

    (6)對根結點的左子樹進行左旋,以下圖第二圖所示。

    (7)對根結點進行右旋,以下圖第三圖所示,完成平衡操做。


    右平衡(使右邊平衡)旋轉處理的函數代碼以下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*  對以指針T所指結點爲根的二叉樹做右平衡旋轉處理, */
/*  本算法結束時,指針T指向新的根結點 */
void RightBalance(BiTree *T)
{
     BiTree R,Rl;
     R=(*T)->rchild; /*  R指向T的右子樹根結點 */
     switch (R->bf)
     { /*  檢查T的右子樹的平衡度,並做相應平衡處理 */
      case RH: /*  新結點插入在T的右孩子的右子樹上,要做單左旋處理 */
               (*T)->bf=R->bf=EH;
               L_Rotate(T);
               break ;
      case LH: /*  新結點插入在T的右孩子的左子樹上,要做雙旋處理 */
               Rl=R->lchild; /*  Rl指向T的右孩子的左子樹根 */
               switch (Rl->bf)
               { /*  修改T及其右孩子的平衡因子 */
                 case RH: (*T)->bf=LH;
                          R->bf=EH;
                          break ;
                 case EH: (*T)->bf=R->bf=EH;
                          break ;
                 case LH: (*T)->bf=EH;
                          R->bf=RH;
                          break ;
               }
               Rl->bf=EH;
               R_Rotate(&(*T)->rchild); /*  對T的右子樹做右旋平衡處理 */
               L_Rotate(T); /*  對T做左旋平衡處理 */
     }
}

     插入數據操做以下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*  若在平衡的二叉排序樹T中不存在和e有相同關鍵字的結點,則插入一個 */
/*  數據元素爲e的新結點,並返回1,不然返回0。若因插入而使二叉排序樹 */
/*  失去平衡,則做平衡旋轉處理,布爾變量taller反映T長高與否。 */
Status InsertAVL(BiTree *T, int e,Status *taller)
     if (!*T)
     {
         /*  插入新結點,樹「長高」,置taller爲TRUE */
         *T=(BiTree) malloc ( sizeof (BitNode));
         (*T)->data=e; (*T)->lchild=(*T)->rchild=NULL; (*T)->bf=EH;
         *taller=TRUE;
     }
     else
     {
         if (e==(*T)->data)
         {
             /*  樹中已存在和e有相同關鍵字的結點則再也不插入 */
             *taller=FALSE; return FALSE;
         }
         if (e<(*T)->data)
         {
             /*  應繼續在T的左子樹中進行搜索 */
             if (!InsertAVL(&(*T)->lchild,e,taller)) /*  未插入 */
                 return FALSE;
             if (*taller)             /*   已插入到T的左子樹中且左子樹「長高」 */
                 switch ((*T)->bf) /*  檢查T的平衡度 */
                 {
                     case LH:        /*  本來左子樹比右子樹高,須要做左平衡處理 */
                         LeftBalance(T);
                         *taller=FALSE; break ;
                     case EH:        /*  本來左、右子樹等高,現因左子樹增高而使樹增高 */
                         (*T)->bf=LH;
                         *taller=TRUE; break ;
                     case RH:        /*  本來右子樹比左子樹高,現左、右子樹等高 */ 
                         (*T)->bf=EH;
                         *taller=FALSE; break ;
                 }
         }
         else
         { /*  應繼續在T的右子樹中進行搜索 */
             if (!InsertAVL(&(*T)->rchild,e,taller)) /*  未插入 */
                 return FALSE;
             if (*taller)          /*  已插入到T的右子樹且右子樹「長高」 */
                 switch ((*T)->bf) /*  檢查T的平衡度 */
                 {
                     case LH:     /*  本來左子樹比右子樹高,現左、右子樹等高 */
                         (*T)->bf=EH;
                         *taller=FALSE;  break ;
                     case EH:     /*  本來左、右子樹等高,現因右子樹增高而使樹增高  */
                         (*T)->bf=RH;
                         *taller=TRUE; break ;
                     case RH:     /*  本來右子樹比左子樹高,須要做右平衡處理 */
                         RightBalance(T);
                         *taller=FALSE; break ;
                 }
         }
     }
     return TRUE;
}

     說明:

    (1)程序開始執行時,若是T爲空時,則申請內存新增一個結點。

    (2)若是表示當存在相同結點,則不須要插入。

    (3)當新結點e小於T的根結點時,則在T的左子樹查找。

    (4)遞歸調用本函數,直到找到則返回FALSE,不然說明插入結點成功,執行下面語句。

    (5)當taller爲TRUE時,說明插入結點,此時須要判斷T的平衡因子,若是是1,說明左子樹高於右子樹,須要調用LeftBalance函數進行左平衡旋轉處理。若是爲0或-1,則說明新插入的結點沒有讓整棵二叉排序樹失去平衡性,只需修改相關BF值便可。

    (6)說明結點e大於T的根結點的值,在T的右子樹查找。與上面相似。

 

    刪除結點的代碼以下所示。


三、C語言實現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
<strong>#include <stdio.h>   
#include <stdlib.h>  
 
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100                     /* 存儲空間初始分配量 */
 
typedef int Status;                     /* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
 
 
/* 二叉樹的二叉鏈表結點結構定義 */
typedef  struct BitNode                 /* 結點結構 */
{
     int data;                           /* 結點數據 */
     int bf;                             /*  結點的平衡因子 */
     struct BitNode *lchild, *rchild;    /* 左右孩子指針 */
} BitNode, *BiTree;
 
 
/* 對以p爲根的二叉排序樹做右旋處理 */
/* 處理以後p指向新的樹根結點,即旋轉處理以前的左子樹的根結點 */
//右旋-順時針旋轉(如LL型就得對根結點作該旋轉)
void R_Rotate(BiTree *P)
{
     BiTree L;
     L=(*P)->lchild;                      /*  L指向P的左子樹根結點 */
     (*P)->lchild=L->rchild;               /*  L的右子樹掛接爲P的左子樹 */
     L->rchild=(*P);
     *P=L;                               /*  P指向新的根結點 */
}
 
/* 對以P爲根的二叉排序樹做左旋處理, */
/* 處理以後P指向新的樹根結點,即旋轉處理以前的右子樹的根結點0  */
//左旋-逆時針旋轉(如RR型就得對根結點作該旋轉)
void L_Rotate(BiTree *P)
{
     BiTree R;
     R = (*P)->rchild;                    /* R指向P的右子樹根結點 */
     (*P)->rchild = R->lchild;         /* R的左子樹掛接爲P的右子樹 */
     R->lchild = (*P);
     *P = R;                             /* P指向新的根結點 */
}
 
#define LH +1                           /*  左高 */
#define EH 0                            /*  等高 */
#define RH -1                           /*  右高 */
 
/*  對以指針T所指結點爲根的二叉樹做左平衡旋轉處理 */
/*  本算法結束時,指針T指向新的根結點 */
void LeftBalance(BiTree *T)
{
     BiTree L,Lr;
     L = (*T)->lchild;                    /*  L指向T的左子樹根結點 */
     switch (L->bf)
     {
         /* 檢查T的左子樹的平衡度,並做相應平衡處理 */
         case LH:                        /* 新結點插入在T的左孩子的左子樹上,要做單右旋處理 */
             (*T)->bf=L->bf=EH;
             R_Rotate(T);
             break ;
         case RH:                        /* 新結點插入在T的左孩子的右子樹上,要做雙旋處理 */ //
             Lr=L->rchild;                /* Lr指向T的左孩子的右子樹根 */
             switch (Lr->bf)
             {  
                 /* 修改T及其左孩子的平衡因子 */
                 case LH:
                     (*T)->bf=RH;
                     L->bf=EH;
                     break ;
                 case EH:
                     (*T)->bf=L->bf=EH;
                     break ;
                 case RH:
                     (*T)->bf=EH;
                     L->bf=LH;
                     break ;
             }
             Lr->bf=EH;
             L_Rotate(&(*T)->lchild); /* 對T的左子樹做左旋平衡處理 */
             R_Rotate(T);                /* 對T做右旋平衡處理 */
     }
}
 
/*  對以指針T所指結點爲根的二叉樹做右平衡旋轉處理, */
/*  本算法結束時,指針T指向新的根結點 */
void RightBalance(BiTree *T)
{
     BiTree R,Rl;
     R=(*T)->rchild;                      /*  R指向T的右子樹根結點 */
     switch (R->bf)
     {
         /*  檢查T的右子樹的平衡度,並做相應平衡處理 */
         case RH:                        /*  新結點插入在T的右孩子的右子樹上,要做單左旋處理 */
             (*T)->bf=R->bf=EH;
             L_Rotate(T);
             break ;
         case LH:                        /*  新結點插入在T的右孩子的左子樹上,要做雙旋處理 */ //最小不平衡樹的根結點爲負,其右孩子爲正
             Rl=R->lchild;                /*  Rl指向T的右孩子的左子樹根 */
             switch (Rl->bf)
             {
                 /*  修改T及其右孩子的平衡因子 */
                 case RH:
                     (*T)->bf=LH;
                     R->bf=EH;
                     break ;
                 case EH:
                     (*T)->bf=R->bf=EH;
                     break ;
                 case LH:
                     (*T)->bf=EH;
                     R->bf=RH;
                     break ;
             }
             Rl->bf=EH;
             R_Rotate(&(*T)->rchild); /*  對T的右子樹做右旋平衡處理 */
             L_Rotate(T);                /*  對T做左旋平衡處理 */
     }
}
 
/*  若在平衡的二叉排序樹T中不存在和e有相同關鍵字的結點,則插入一個 */
/*  數據元素爲e的新結點,並返回1,不然返回0。若因插入而使二叉排序樹 */
/*  失去平衡,則做平衡旋轉處理,布爾變量taller反映T長高與否。 */
Status InsertAVL(BiTree *T, int e,Status *taller)
     if (!*T)
     {
         /*  插入新結點,樹「長高」,置taller爲TRUE */
         *T=(BiTree) malloc ( sizeof (BitNode));
         (*T)->data=e;
         (*T)->lchild=(*T)->rchild=NULL;
         (*T)->bf=EH;
         *taller=TRUE;
     }
     else
     {
         if (e==(*T)->data)
         {
             /*  樹中已存在和e有相同關鍵字的結點則再也不插入 */
             *taller=FALSE;
             return FALSE;
         }
         if (e<(*T)->data)
         {
             /*  應繼續在T的左子樹中進行搜索 */
             if (!InsertAVL(&(*T)->lchild, e, taller)) /*  未插入 */
                 return FALSE;
             if (*taller)                             /*   已插入到T的左子樹中且左子樹「長高」 */
                 switch ((*T)->bf)                 /*  檢查T的平衡度 */
                 {
                     case LH:                        /*  本來左子樹比右子樹高,須要做左平衡處理 */
                         LeftBalance(T);
                         *taller=FALSE;
                         break ;
                     case EH:                        /*  本來左、右子樹等高,現因左子樹增高而使樹增高 */
                         (*T)->bf=LH;
                         *taller=TRUE;
                         break ;
                     case RH:                        /*  本來右子樹比左子樹高,現左、右子樹等高 */ 
                         (*T)->bf=EH;
                         *taller=FALSE;
                         break ;
                 }
         }
         else
         {
             /*  應繼續在T的右子樹中進行搜索 */
             if (!InsertAVL(&(*T)->rchild,e, taller)) /*  未插入 */
             {
                 return FALSE;
             }
             if (*taller)                             /*  已插入到T的右子樹且右子樹「長高」 */
             {
                 switch ((*T)->bf)                 /*  檢查T的平衡度 */
                 {
                     case LH:                        /*  本來左子樹比右子樹高,現左、右子樹等高 */
                         (*T)->bf=EH;
                         *taller=FALSE; 
相關文章
相關標籤/搜索