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