1. 程式人生 > 實用技巧 >java-平衡二叉樹

java-平衡二叉樹

1,前面一條部落格實現了,二叉查詢樹,但是當二叉樹退化成連結串列後,二叉查詢樹的時間複雜度優越性就沒有了,

再網上學習了一下,啊啊啊,調了一夜bug,5555

主要參考博主的部落格:https://www.cnblogs.com/qm-article/p/9349681.html

  • 實現平衡二叉樹是基於,二叉查詢樹的,所以建議先寫一遍二叉查詢樹,再學二叉平衡操作原理,把自己的二叉查詢樹變成,平衡二叉樹。(可以一步一步,先實現新增節點-新增完就是一顆排序樹,中序遍歷就是排序序列,再實現刪除功能,就完成了二叉查詢樹,再實現平衡功能就是平衡二叉樹)
  • 我自己的二叉查詢樹:上一篇部落格,參考了博主的文章後,將Node節點新增了一個指向父節點的指標,這樣不用特地去表記preNode,也便於回溯平衡操作,因為先將子樹平衡,母樹才能平衡。 但是增加了一個指標(java裡其實是引用,一個意思,只不過用物件代替了),再新增節點和刪除節點的時候的指標指向要特別注意,調式bug 也主要在這裡,如果父指標沒有修改,回溯的時候不知道會回溯到哪
  • 平衡二叉樹的核心還是平衡操作,學習:https://www.cnblogs.com/qm-article/p/9349681.html,博主分析的還是挺清晰的,我在bb一遍,加深一下我自己的印象,233

網上搜的線上動態生存二叉樹、平衡二叉樹的網站:https://visualgo.net/zh/bst

2 原理:

2.1當平衡的樹節點變動後(新增刪除操作),變得非平衡了,因為平衡狀態根節點左右子樹高度差為-1、0、1。所以可以列舉一下新增節點失衡前的狀態高度差為1(-1與之對稱):即再新增就會失衡,這三種是節點高度差為2(-2的對稱一下),即需要右旋才能平衡的狀態。

----父節點或者null(A為根節點)

2.2,列舉失衡狀態

左邊4狀態其實等於左邊兩種,中間的兩種,不看A節點的話,就是左邊兩種狀態,

①關於左上狀態,A節點高度差為2,直接右旋即可,B.father=A.father;B.right=A,A.father=B;A.right=B.left(null);

②左下的狀態,A的高度差為2,但不能直接右旋,先以B為根節點將子樹左旋為後,再進行如左上①右旋

③右下,如果直接右旋如圖:

要先以B為根左旋,以A為根右旋

④右上,產生高度差的節點是A,如果直接右旋會如圖:

等於沒旋,還是不平衡,要先以B節點為根左旋,B為根節點左旋又牽扯到左下②的情況,先右旋再左旋,再以A為節點右旋

總結一下就是:觸發高度差=2/-2的節點(要右/左旋)nodeA,其左子節點nodeB,若nodeB無子節點,則直接右/左旋,若有子節點,先以nodeB為根節點左/右旋,再以nodeA為根節點右/左旋

右旋程式碼:

 1 public Node rightRotation(Node nodeA){
 2     if(nodeA==null)
 3         return null;
 4     else{
 5         Node nodeB=nodeA.lchild;
 6         nodeA.lchild=nodeB.rchild;
 7         if(nodeB.rchild!=null)
 8             nodeB.rchild.father=nodeA;
 9         nodeB.rchild=nodeA;
10         nodeB.father=nodeA.father;
11         if(nodeA.father==null)
12             this.root=nodeB;
13         else{
14             if(nodeA.father.lchild==nodeA)
15                 nodeA.father.lchild=nodeB;
16             else
17                 nodeA.father.rchild=nodeB;
18         }
19         nodeA.father=nodeB;
20         return nodeB;
21     }
22 }
View Code

2.3左旋的情況相反,與左旋對稱,

左旋程式碼:

 1 public Node leftRotation(Node nodeA){
 2         if(nodeA==null)
 3             return null;
 4         else{
 5             Node nodeB=nodeA.rchild;
 6             nodeA.rchild=nodeB.lchild;
 7             if(nodeB.lchild!=null)
 8                 nodeB.lchild.father=nodeA;
 9             nodeB.lchild=nodeA;
10             nodeB.father=nodeA.father;
11             if(nodeA.father==null)
12                 this.root=nodeB;
13             else{
14                 if(nodeA.father.lchild==nodeA)
15                     nodeA.father.lchild=nodeB;
16                 else
17                     nodeA.father.rchild=nodeB;
18             }
19             nodeA.father=nodeB;
20             return nodeB;
21         }
22     }
View Code

判斷----執行哪種旋轉操作,左 / 右 / 左右 / 右左

 1 /**
 2      * 求高度差,函式
 3      */
 4     public int heightDifference(Node root){
 5         return treeHeightRec(root.lchild)-treeHeightRec(root.rchild);
 6     }
 7 
 8     //呼叫平衡
 9 
10 public void banlanceTree(Node p){
11         while(p!=null){
12             //右旋
13             if(heightDifference(p)==2){
14                 Node nodeB=p.lchild;
15                 //如果b有右子節點,先將b左旋,再將a右旋
16                 if(nodeB.rchild!=null){
17                     leftRotation(nodeB);
18                     rightRotation(p);
19                 }
20                 //如果b無右子節點,將a右旋
21                 else
22                     rightRotation(p);
23             }
24             //左旋
25             else if(heightDifference(p)==-2){
26                 Node nodeB=p.rchild;
27                 //如果b有左子節點,先將b右旋,再將a左旋
28                 if(nodeB.lchild!=null){
29                     rightRotation(nodeB);
30                     leftRotation(p);
31                 }
32                 //如果b無左子節點,a左旋
33                 else
34                     leftRotation(p);
35             }
36             p=p.father;
37         }
38     }
View Code

2.4,說一下刪除的時候一些情況,

我剛開始看部落格的時候有一個誤區,一直糾結了很久,最後是因為看的不仔細,沒注意(nodeB只要有子節點,就要先左/右旋,再右/左旋)我原本分了三種情況,①AB均無子節點,直接旋轉,

②A無子節點,B有(就是列舉圖對於左下的圖),要先反旋,再旋,③(我把剩下的都放在這裡了)A有子節點,B也有,就直接旋,把B的子節點給A,直到後來除錯刪除函式的時候遇到一種情況:

旋來旋去都沒用了,回去看部落格才發現自己看的太草率了。

正解是,刪除F後,向上回溯,C高度差為0,繼續,到A,高度差為2要右旋(右旋還分直接旋和間接旋),因為B有右子樹,所以觸發以B為根節點的左旋即:

完整程式碼,

  1 /**
  2  * 二叉查詢樹,在某些情況下二叉樹退化成連結串列,時間複雜度上升,平衡二叉樹就是解決這一問題的
  3  * 建樹過程中,沒插入一個節點都對樹進行平衡
  4  *
  5  * 轉載:https://www.cnblogs.com/qm-article/p/9349681.html
  6  * 借鑑了。Node 新增一個父節點操作
  7  */
  8 
  9 /**
 10  * 這裡有一個bug,就是刪除函式裡面引用了,刪除最大值/最小值函式並且這兩個函式有返回值,
 11  * 再處理被刪節點有左/右子樹,取右子樹最小值代替被刪節點上很方便,但是如果單獨使用刪除大值
 12  * 最小值函式,就無法呼叫平衡操作,牽扯到,返回值,無法從被刪除的節點精準的回溯,這也不只是
 13  * 減少時間的問題,只能反向回溯,如果正向遍歷,根節點的高度差,受子樹根節點高度差影響,只能先解決子樹
 14  * 的平衡問題
 15  *
 16  * 解決方案:直接複製兩個刪除最大值,/最小值函式換個函式名,專供刪除值呼叫,原刪除最大值/最小值函式不用返回值,
 17  *                                                                  (不行,物件還是可以呼叫這4 個函式)
 18  * ②直接將函式寫進delete裡面
 19  *寫進去太delete函式太長了,這bug 不象處理了,233,--搞吧,寫完以後應該也不會再弄了,調式的很煩啊,啊啊啊啊
 20  *
 21  */
 22 
 23 
 24 import java.util.LinkedList;
 25 import java.util.Queue;
 26 public class AVLTree {
 27 
 28     public class Node{
 29         private Node father=null;
 30         private Node lchild=null;
 31         private Node rchild=null;
 32 
 33         private int data=Integer.MAX_VALUE;
 34 
 35         public Node(){}
 36         public Node(int data){
 37             this.data=data;
 38         }
 39     }
 40 
 41 
 42     private Node root=null;
 43 
 44     /**
 45      * 複製上一篇,主要是插入時,新增父節點指標,新增完成,平衡
 46      */
 47     //是否存在
 48     public boolean isExist(Node root,int data){
 49         if(root==null)//樹空
 50             return false;
 51         if(root.data==data)
 52             return true;
 53         else if(data<root.data){
 54             if(root.lchild==null)
 55                 return false;
 56             else
 57                 return isExist(root.lchild,data);
 58         }else{
 59             if(root.rchild==null)
 60                 return false;
 61             else
 62                 return isExist(root.rchild,data);
 63         }
 64     }
 65     //插入-建樹
 66     public void inseart(Node root,int data){
 67         if(isExist(root,data)){
 68             System.out.println(data+"已經存在樹中");
 69             return;
 70         }
 71         //樹空插入根節點--不用回溯,直接return
 72         if(root==null){
 73             this.root=new Node(data);
 74             return;
 75         }
 76         else{
 77             //元素不能重複,
 78             if(data<root.data){
 79                 if(root.lchild==null){
 80                     Node newNode=new Node(data);
 81                     root.lchild=newNode;
 82                     newNode.father=root;
 83                 }
 84                 else{
 85                     inseart(root.lchild,data);
 86                 }
 87             }
 88             else{
 89                 if(root.rchild==null){
 90                     Node newNode=new Node(data);
 91                     root.rchild=newNode;
 92                     newNode.father=root;
 93                 }else
 94                     inseart(root.rchild,data);
 95             }
 96         }
 97         //插入完了要回溯,平衡
 98         banlanceTree(root);
 99     }
100 
101     /**刪除-主體複製上一篇,因為新加了,父節點指標,preNode和刪除程式碼那裡需要改一下
102      * 先實現刪除最大值最小值,
103      *
104      * 刪除後-平衡
105      */
106     //修改指標關係
107     public void deleteMin(Node root){
108         //空樹
109         if(root==null){
110             System.out.println("樹空,無最小值");
111             return;
112         }
113         //只有一個根節點
114         else if(root.lchild==null&&root.rchild==null){
115             this.root=null;
116             return;
117         }
118         //根節點無左子樹(刪除根節點)
119         else if(root.lchild==null){
120             this.root=root.rchild;
121             root.rchild.father=null;
122         }
123         //有左子樹且非空
124         while(root.lchild!=null){
125             root=root.lchild;
126         }
127         root.father.lchild=root.rchild;
128         if(root.rchild!=null)
129             root.rchild.father=root.father;
130 
131         root.rchild=null;
132         banlanceTree(root);
133     }
134     //修改指標關係
135     public void deleteMax(Node root){
136         //空樹
137         if(root==null){
138             System.out.println("樹空,無最大值");
139             return;
140         }
141         //只有一個根節點
142         else if(root.lchild==null&&root.rchild==null){
143             if(root.father==null)
144                 this.root=null;
145             return;
146         }
147         //根節點無右子樹(刪除根節點)
148         else if(root.rchild==null){
149             this.root=root.lchild;
150             root.lchild.father=null;
151         }
152         //有右子樹且非空
153         else if(root.rchild!=null){
154             while(root.rchild!=null){
155                 root=root.rchild;
156             }
157             root.father.rchild=root.lchild;
158             if(root.lchild!=null)
159                 root.lchild.father=root.father;
160         }
161         root.lchild=null;
162         banlanceTree(root);
163     }
164 
165 
166 
167     public void delete(Node root,int data){
168         //在樹中(樹非空)
169         if(!isExist(root,data)){
170             System.out.println(data+"值不在樹中");
171             return;
172         }
173         //以下預設在樹中
174 
175         //只有一個節點-即刪除根節點
176         if(root.lchild==null&&root.rchild==null){
177             this.root=null;
178             return;
179         }
180         //先遍歷到節點,在刪除-將替換節點的值賦給該節點,
181         else{
182             while(root.data!=data){
183                 if(data<root.data){
184                     root=root.lchild;
185                 }
186                 else if(data>root.data){
187                     root=root.rchild;
188                 }
189             }
190             //此時root即待刪除節點;
191             //該節點無子節點
192             if(root.lchild==null&&root.rchild==null){
193                 if(root.father.lchild==root){
194                     root.father.lchild=null;
195                 }
196                 else{
197                     root.father.rchild=null;
198                 }
199             }
200             //只有左子節點
201             else if(root.lchild!=null&&root.rchild==null){
202                 //拿到左子樹的最大值,
203                 //Node replaceNode=deleteMax(root.lchild);
204                             Node p=root.lchild;
205                             if(p.lchild==null&&p.rchild==null){
206                                 if(p.father.lchild==p)
207                                     p.father.lchild=null;
208                                 else
209                                     p.father.rchild=null;
210                             }
211                             //根節點無右子樹(刪除根節點)
212                             else if(p.lchild!=null&&p.rchild==null){
213                                     if(p.father.lchild==p){
214                                         p.father.lchild=p.lchild;
215                                         p.lchild.father=p.father;
216                                     }else {
217                                         p.father.rchild=p.lchild;
218                                         p.lchild.father=p.father;
219                                     }
220                             }
221                             //有右子樹且非空
222                             else if(p.rchild!=null){
223                                 while(p.rchild!=null){
224                                     p=p.rchild;
225                                 }
226                                 p.father.rchild=p.lchild;
227                                 if(p.lchild!=null)
228                                     p.lchild.father=p.father;
229                             }
230                             p.lchild=null;
231                 root.data=p.data;
232             }
233             //有右子節點
234             else if(root.rchild!=null){
235                 //拿到右子樹的最小值
236                 //Node replaceNode=deleteMin(root.rchild);
237                                 Node p=root.rchild;
238                                 if(p.lchild==null&&p.rchild==null){
239                                     if(p.father.lchild==p)
240                                         p.father.lchild=null;
241                                     else
242                                         p.father.rchild=null;
243                                 }
244                                 //根節點無左子樹(刪除根節點)
245                                 else if(p.lchild==null){
246                                     if(p.father.lchild==p){
247                                         p.father.lchild=p.rchild;
248                                         p.rchild.father=p.father;
249                                     }else {
250                                         p.father.rchild=p.rchild;
251                                         p.rchild.father=p.father;
252                                     }
253                                 }
254                                 //有左子樹且非空
255                                 while(p.lchild!=null){
256                                     p=p.lchild;
257                                 }
258                                 p.father.lchild=p.rchild;
259                                 if(p.rchild!=null)
260                                     p.rchild.father=p.father;
261 
262                                 p.rchild=null;
263                 root.data=p.data;
264             }
265         }
266         //刪除完要回溯,平衡
267         Node p=root.father;
268         //System.out.println("p1 "+p.data);
269         banlanceTree(p);
270     }
271 
272 
273 
274     /**輔助函式-從前一篇複製
275      * 求樹高
276      * 列印樹
277      */
278     //遞迴求樹高-用於列印樹
279     //遞迴
280     public int treeHeightRec(Node root){
281         if(root==null)
282             return 0;
283         else{
284             int a =treeHeightRec(root.lchild);
285             int b = treeHeightRec(root.rchild);
286             int a1=(a>b)?(a+1):(b+1);
287             return (a>b)?(a+1):(b+1);
288         }
289     }
290     //列印樹--233,複製前一篇的方法,如果樹層數很深時,列印的比較彆扭
291     public void printTree(Node root){
292         int H=treeHeightRec(root);
293         int h=H;
294         //System.out.println("樹高:"+H);
295         if(H==0)
296             System.out.println("樹空,無列印");
297         else{
298             System.out.println("列印樹:");
299             Queue<Node> queue=new LinkedList<>();
300             queue.add(root);
301             int height=1;
302             //記錄每層孩子個數
303             int len=1;
304             while(h>0){
305                 int length=0;
306                 String space="";
307                 for(int i=0;i<(((Math.pow(2,H)+1)*3)/(Math.pow(2,height)+1));i++)
308                     space+=" ";
309                 for(int i=0;i<len;i++){
310                     Node curroot=queue.poll();
311                     if(curroot.data==Integer.MAX_VALUE){
312                         System.out.print(space);
313                     }else
314                         System.out.print(space+curroot.data);
315 
316                     if(curroot.lchild!=null){
317                         queue.add(curroot.lchild);
318                     }
319                     else
320                         queue.add(new Node());
321                     length++;
322                     if(curroot.rchild!=null){
323                         queue.add(curroot.rchild);
324                     }
325                     else
326                         queue.add(new Node());
327                     length++;
328                 }
329                 System.out.println();
330                 System.out.println();
331                 len=length;
332                 height++;
333                 h--;
334             }
335             System.out.println();
336         }
337     }
338     //中序遍歷
339     public void inOrder(Node root){
340         if(root==null)
341             return;
342         inOrder(root.lchild);
343         System.out.print(root.data+" ");
344         inOrder(root.rchild);
345     }
346 
347 
348     /**
349      * 平衡操作
350      * 參考與https://www.cnblogs.com/qm-article/p/9349681.html
351      *①先判斷失衡,及執行那種平衡操作
352      * ②平衡操作。左旋,右旋,先左再右旋,先右旋再左旋
353      *
354      */
355 
356     public Node rightRotation(Node nodeA){
357         if(nodeA==null)
358             return null;
359         else{
360             Node nodeB=nodeA.lchild;
361             nodeA.lchild=nodeB.rchild;
362             if(nodeB.rchild!=null)
363                 nodeB.rchild.father=nodeA;
364             nodeB.rchild=nodeA;
365             nodeB.father=nodeA.father;
366             if(nodeA.father==null)
367                 this.root=nodeB;
368             else{
369                 if(nodeA.father.lchild==nodeA)
370                     nodeA.father.lchild=nodeB;
371                 else
372                     nodeA.father.rchild=nodeB;
373             }
374             nodeA.father=nodeB;
375             return nodeB;
376         }
377     }
378 
379     public Node leftRotation(Node nodeA){
380         if(nodeA==null)
381             return null;
382         else{
383             Node nodeB=nodeA.rchild;
384             nodeA.rchild=nodeB.lchild;
385             if(nodeB.lchild!=null)
386                 nodeB.lchild.father=nodeA;
387             nodeB.lchild=nodeA;
388             nodeB.father=nodeA.father;
389             if(nodeA.father==null)
390                 this.root=nodeB;
391             else{
392                 if(nodeA.father.lchild==nodeA)
393                     nodeA.father.lchild=nodeB;
394                 else
395                     nodeA.father.rchild=nodeB;
396             }
397             nodeA.father=nodeB;
398             return nodeB;
399         }
400     }
401 
402     /**
403      * 求高度差,函式
404      */
405     public int heightDifference(Node root){
406         return treeHeightRec(root.lchild)-treeHeightRec(root.rchild);
407     }
408 
409     //呼叫平衡
410     public void banlanceTree(Node p){
411         while(p!=null){
412             //右旋
413             if(heightDifference(p)==2){
414                 Node nodeB=p.lchild;
415                 //如果b有右子節點,先將b左旋,再將a右旋
416                 if(nodeB.rchild!=null){
417                     leftRotation(nodeB);
418                     rightRotation(p);
419                 }
420                 //如果b無右子節點,將a右旋
421                 else
422                     rightRotation(p);
423             }
424             //左旋
425             else if(heightDifference(p)==-2){
426                 Node nodeB=p.rchild;
427                 //如果b有左子節點,先將b右旋,再將a左旋
428                 if(nodeB.lchild!=null){
429                     rightRotation(nodeB);
430                     leftRotation(p);
431                 }
432                 //如果b無左子節點,a左旋
433                 else
434                     leftRotation(p);
435             }
436             p=p.father;
437         }
438     }
439 
440 
441 
442     public static void main(String args[]) {
443         AVLTree t=new AVLTree();
444         t.inseart(t.root,8);
445         t.inseart(t.root,5);
446         t.inseart(t.root,1);
447         t.inseart(t.root,11);
448         t.inseart(t.root,9);
449         t.printTree(t.root);
450         t.inseart(t.root,13);
451         t.printTree(t.root);
452         t.inseart(t.root,7);
453         t.inseart(t.root,6);
454         t.inseart(t.root,12);
455         t.printTree(t.root);
456         t.inseart(t.root,16);
457         t.printTree(t.root);
458 
459         //刪值
460         t.delete(t.root,9);
461         t.printTree(t.root);
462         t.delete(t.root,8);
463         t.printTree(t.root);
464         t.delete(t.root,7);
465         t.printTree(t.root);
466         t.delete(t.root,5);
467         t.printTree(t.root);
468         t.delete(t.root,8);
469         t.printTree(t.root);
470 
471            //刪最大值
472 //        t.deleteMax(t.root);
473 //        t.printTree(t.root);
474 //        t.deleteMax(t.root);
475 //        t.printTree(t.root);
476 //        t.deleteMax(t.root);
477 //        t.printTree(t.root);
478 //        t.deleteMax(t.root);
479 //        t.printTree(t.root);
480 
481 
482            //刪最小值
483 //        t.deleteMin(t.root);
484 //        t.printTree(t.root);
485 //        t.deleteMin(t.root);
486 //        t.printTree(t.root);
487 //        t.deleteMin(t.root);
488 //        t.printTree(t.root);
489 //        t.deleteMin(t.root);
490 //        t.printTree(t.root);
491 
492         t.inOrder(t.root);
493     }
494 
495 
496 }
View Code

結語,雖然我的樹狀列印函式寫的很撈,>4層之後誤差較大,肉眼可見,但是除錯的時候還是幫了大忙

 1 /**
 2      * 思路:先確定樹高h-最後一層2^(h-1)個數,好知道根節點放在哪裡(預留空間保證根再中間)
 3      * 設每個data三位數=space,元素之間空三位數,每行 (2^H+1)*3 個“ ”
 4      * _數_數_數_數_數_   每層2^(h-1)個數,2^(h-1)+1個space,最後一行長2^h+1個space
 5      *
 6      * 當層數多的時候稍亂,2333不想弄了,
 7      */
 8 
 9 /**輔助函式-從前一篇複製
10      * 求樹高
11      * 列印樹
12      */
13     //遞迴求樹高-用於列印樹
14     //遞迴
15     public int treeHeightRec(Node root){
16         if(root==null)
17             return 0;
18         else{
19             int a =treeHeightRec(root.lchild);
20             int b = treeHeightRec(root.rchild);
21             int a1=(a>b)?(a+1):(b+1);
22             return (a>b)?(a+1):(b+1);
23         }
24     }
25     //列印樹--233,複製前一篇的方法,如果樹層數很深時,列印的比較彆扭
26     public void printTree(Node root){
27         int H=treeHeightRec(root);
28         int h=H;
29         //System.out.println("樹高:"+H);
30         if(H==0)
31             System.out.println("樹空,無列印");
32         else{
33             System.out.println("列印樹:");
34             Queue<Node> queue=new LinkedList<>();
35             queue.add(root);
36             int height=1;
37             //記錄每層孩子個數
38             int len=1;
39             while(h>0){
40                 int length=0;
41                 String space="";
42                 for(int i=0;i<(((Math.pow(2,H)+1)*3)/(Math.pow(2,height)+1));i++)
43                     space+=" ";
44                 for(int i=0;i<len;i++){
45                     Node curroot=queue.poll();
46                     if(curroot.data==Integer.MAX_VALUE){
47                         System.out.print(space);
48                     }else
49                         System.out.print(space+curroot.data);
50 
51                     if(curroot.lchild!=null){
52                         queue.add(curroot.lchild);
53                     }
54                     else
55                         queue.add(new Node());
56                     length++;
57                     if(curroot.rchild!=null){
58                         queue.add(curroot.rchild);
59                     }
60                     else
61                         queue.add(new Node());
62                     length++;
63                 }
64                 System.out.println();
65                 System.out.println();
66                 len=length;
67                 height++;
68                 h--;
69             }
70             System.out.println();
71         }
72     }
View Code