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 }
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 }
判断----执行哪种旋转操作,左 / 右 / 左右 / 右左

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 }
平衡函数在每一次变动树元素之后调用----添加函数,删除函数,删除最大值,删除最小值。
2.4,说一下删除的时候一些情况,
我刚开始看博客的时候有一个误区,一直纠结了很久,最后是因为看的不仔细,没注意(nodeB只要有子节点,就要先左/右旋,再右/左旋)我原本分了三种情况,①AB均无子节点,直接旋转,
②A无右子节点,B有(就是枚举图对于左下的图),要先反旋,再旋,③(我把剩下的都放在这里了)A有右子节点,B也有,就直接旋,把B的子节点给A,直到后来调试删除函数的时候遇到一种情况:
F是待删节点,删掉后回溯到C,高度差为0,继续回溯到A,高度差为2,AB都有右子节点,右旋,交接子节点(子树)。
然后发现,旋来旋去都没用了,回去看博客才发现自己看的太草率了。
正解是,删除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 else if(p.lchild!=null){ 256 while(p.lchild!=null){ 257 p=p.lchild; 258 } 259 p.father.lchild=p.rchild; 260 if(p.rchild!=null) 261 p.rchild.father=p.father; 262 } 263 264 265 p.rchild=null; 266 root.data=p.data; 267 } 268 } 269 //删除完要回溯,平衡 270 Node p=root.father; 271 //System.out.println("p1 "+p.data); 272 banlanceTree(p); 273 } 274 275 276 277 /**辅助函数-从前一篇复制 278 * 求树高 279 * 打印树 280 */ 281 //递归求树高-用于打印树 282 //递归 283 public int treeHeightRec(Node root){ 284 if(root==null) 285 return 0; 286 else{ 287 int a =treeHeightRec(root.lchild); 288 int b = treeHeightRec(root.rchild); 289 return (a>b)?(a+1):(b+1); 290 } 291 } 292 //打印树--233,复制前一篇的方法,如果树层数很深时,打印的比较别扭 293 public void printTree(Node root){ 294 int H=treeHeightRec(root); 295 int h=H; 296 //System.out.println("树高:"+H); 297 if(H==0) 298 System.out.println("树空,无打印"); 299 else{ 300 System.out.println("打印树:"); 301 Queue<Node> queue=new LinkedList<>(); 302 queue.add(root); 303 int height=1; 304 //记录每层孩子个数 305 int len=1; 306 while(h>0){ 307 int length=0; 308 String space=""; 309 for(int i=0;i<(((Math.pow(2,H)+1)*3)/(Math.pow(2,height)+1));i++) 310 space+=" "; 311 for(int i=0;i<len;i++){ 312 Node curroot=queue.poll(); 313 if(curroot.data==Integer.MAX_VALUE){ 314 System.out.print(space); 315 }else 316 System.out.print(space+curroot.data); 317 318 if(curroot.lchild!=null){ 319 queue.add(curroot.lchild); 320 } 321 else 322 queue.add(new Node()); 323 length++; 324 if(curroot.rchild!=null){ 325 queue.add(curroot.rchild); 326 } 327 else 328 queue.add(new Node()); 329 length++; 330 } 331 System.out.println(); 332 System.out.println(); 333 len=length; 334 height++; 335 h--; 336 } 337 System.out.println(); 338 } 339 } 340 //中序遍历 341 public void inOrder(Node root){ 342 if(root==null) 343 return; 344 inOrder(root.lchild); 345 System.out.print(root.data+" "); 346 inOrder(root.rchild); 347 } 348 349 350 /** 351 * 平衡操作 352 * 参考与https://www.cnblogs.com/qm-article/p/9349681.html 353 *①先判断失衡,及执行那种平衡操作 354 * ②平衡操作。左旋,右旋,先左再右旋,先右旋再左旋 355 * 356 */ 357 358 public Node rightRotation(Node nodeA){ 359 if(nodeA==null) 360 return null; 361 else{ 362 Node nodeB=nodeA.lchild; 363 nodeA.lchild=nodeB.rchild; 364 if(nodeB.rchild!=null) 365 nodeB.rchild.father=nodeA; 366 nodeB.rchild=nodeA; 367 nodeB.father=nodeA.father; 368 if(nodeA.father==null) 369 this.root=nodeB; 370 else{ 371 if(nodeA.father.lchild==nodeA) 372 nodeA.father.lchild=nodeB; 373 else 374 nodeA.father.rchild=nodeB; 375 } 376 nodeA.father=nodeB; 377 return nodeB; 378 } 379 } 380 381 public Node leftRotation(Node nodeA){ 382 if(nodeA==null) 383 return null; 384 else{ 385 Node nodeB=nodeA.rchild; 386 nodeA.rchild=nodeB.lchild; 387 if(nodeB.lchild!=null) 388 nodeB.lchild.father=nodeA; 389 nodeB.lchild=nodeA; 390 nodeB.father=nodeA.father; 391 if(nodeA.father==null) 392 this.root=nodeB; 393 else{ 394 if(nodeA.father.lchild==nodeA) 395 nodeA.father.lchild=nodeB; 396 else 397 nodeA.father.rchild=nodeB; 398 } 399 nodeA.father=nodeB; 400 return nodeB; 401 } 402 } 403 404 /** 405 * 求高度差,函数 406 */ 407 public int heightDifference(Node root){ 408 return treeHeightRec(root.lchild)-treeHeightRec(root.rchild); 409 } 410 411 //调用平衡 412 public void banlanceTree(Node p){ 413 while(p!=null){ 414 //右旋 415 if(heightDifference(p)==2){ 416 Node nodeB=p.lchild; 417 //如果b有右子节点,先将b左旋,再将a右旋 418 if(nodeB.rchild!=null){ 419 leftRotation(nodeB); 420 rightRotation(p); 421 } 422 //如果b无右子节点,将a右旋 423 else 424 rightRotation(p); 425 } 426 //左旋 427 else if(heightDifference(p)==-2){ 428 Node nodeB=p.rchild; 429 //如果b有左子节点,先将b右旋,再将a左旋 430 if(nodeB.lchild!=null){ 431 rightRotation(nodeB); 432 leftRotation(p); 433 } 434 //如果b无左子节点,a左旋 435 else 436 leftRotation(p); 437 } 438 p=p.father; 439 } 440 } 441 442 443 444 public static void main(String args[]) { 445 AVLTree t=new AVLTree(); 446 t.inseart(t.root,8); 447 t.inseart(t.root,5); 448 t.inseart(t.root,1); 449 t.inseart(t.root,11); 450 t.inseart(t.root,9); 451 t.printTree(t.root); 452 t.inseart(t.root,13); 453 t.printTree(t.root); 454 t.inseart(t.root,7); 455 t.inseart(t.root,6); 456 t.inseart(t.root,12); 457 t.printTree(t.root); 458 t.inseart(t.root,16); 459 t.printTree(t.root); 460 461 //删值 462 //t.delete(t.root,9); 463 //t.printTree(t.root); 464 //t.delete(t.root,8); 465 //t.printTree(t.root); 466 // t.delete(t.root,7); 467 // t.printTree(t.root); 468 t.delete(t.root,5); 469 t.printTree(t.root); 470 t.delete(t.root,8); 471 t.printTree(t.root); 472 473 //删最大值 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 // t.deleteMax(t.root); 481 // t.printTree(t.root); 482 483 484 //删最小值 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 // t.deleteMin(t.root); 492 // t.printTree(t.root); 493 494 t.inOrder(t.root); 495 } 496 497 498 }
结语,虽然我的树状打印函数写的很捞,>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 }