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

b61fe20f1e62ee5b21bc35731f3a9863.png

2 原理:

2.1当平衡的树节点变动后(添加删除操作),变得非平衡了,因为平衡状态根节点左右子树高度差为-1、0、1。所以可以枚举一下添加节点失衡前的状态高度差为1(-1与之对称):即再添加就会失衡,这三种是节点高度差为2(-2的对称一下),即需要右旋才能平衡的状态。

----父节点或者null(A为根节点)

495baf787a2868d294d2cba9e6d80669.png

2.2,枚举失衡状态

d1e71c53ca93aa1b43e1d9b9378e0e16.png

左边4状态其实等于左边两种,中间的两种,不看A节点的话,就是左边两种状态,

①关于左上状态,A节点高度差为2,直接右旋即可,B.father=A.father;B.right=A,A.father=B;A.right=B.left(null);

40b835a84817f79df30ec04a5d8003a4.png

②左下的状态,A的高度差为2,但不能直接右旋,先以B为根节点将子树左旋为后,再进行如左上①右旋

930f2389f96d5ea8a8c7eb2268289ac9.png

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

22247ae18ec108d123ebf8665f540225.png

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

bf74025c99c145aa42006dce38080216.png

2839d0f8cc4f5f37e7dd4965bdc39947.png

④右上,产生高度差的节点是A,如果直接右旋会如图:

97e3a4a0b0e89bd2c25164930afde08b.png

等于没旋,还是不平衡,要先以B节点为根左旋,B为根节点左旋又牵扯到左下②的情况,先右旋再左旋,再以A为节点右旋

15f42233af502ac88b955a2fb84c3961.png

32300efff1cd453a65413bcbcbe212eb.png

cef7a967f99a80c78d89b7da42403819.png

总结一下就是:触发高度差=2/-2的节点(要右/左旋)nodeA,其左子节点nodeB,若nodeB无子节点,则直接右/左旋,若有子节点,先以nodeB为根节点左/右旋,再以nodeA为根节点右/左旋

右旋代码:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 publicNode 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 returnnodeB;21 }22 }

View Code

2.3左旋的情况相反,与左旋对称,

左旋代码:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 publicNode 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 returnnodeB;21 }22 }

View Code

判断----执行哪种旋转操作,左 / 右 / 左右 / 右左

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 /**

2 * 求高度差,函数3 */

4 public intheightDifference(Node root){5 return treeHeightRec(root.lchild)-treeHeightRec(root.rchild);6 }7

8 //调用平衡

9

10 public voidbanlanceTree(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,AB都有右子节点,右旋,交接子节点(子树)。

c15099bc71ffac9470bd67fff086191a.png

7120fbc3716f438579bf2832863d1469.png

e4f866a91aa924507c54dbe135cbbe71.png

然后发现,旋来旋去都没用了,回去看博客才发现自己看的太草率了。

正解是,删除F后,向上回溯,C高度差为0,继续,到A,高度差为2要右旋(右旋还分直接旋和间接旋),因为B有右子树,所以触发以B为根节点的左旋即:

7d1b4e7e6486845df92e170a7c3e63ee.png        

c8a1c0e2824f891aea56c03876bdba93.png        

fa754b01147b1b324787d42dda5ed6b7.png

完整代码,

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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 importjava.util.LinkedList;25 importjava.util.Queue;26 public classAVLTree {27

28 public classNode{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 publicNode(){}36 public Node(intdata){37 this.data=data;38 }39 }40

41

42 private Node root=null;43

44 /**

45 * 复制上一篇,主要是插入时,添加父节点指针,添加完成,平衡46 */

47 //是否存在

48 public boolean isExist(Node root,intdata){49 if(root==null)//树空

50 return false;51 if(root.data==data)52 return true;53 else if(data

57 returnisExist(root.lchild,data);58 }else{59 if(root.rchild==null)60 return false;61 else

62 returnisExist(root.rchild,data);63 }64 }65 //插入-建树

66 public void inseart(Node root,intdata){67 if(isExist(root,data)){68 System.out.println(data+"已经存在树中");69 return;70 }71 //树空插入根节点--不用回溯,直接return

72 if(root==null){73 this.root=newNode(data);74 return;75 }76 else{77 //元素不能重复,

78 if(data

94 inseart(root.rchild,data);95 }96 }97 //插入完了要回溯,平衡

98 banlanceTree(root);99 }100

101 /**删除-主体复制上一篇,因为新加了,父节点指针,preNode和删除代码那里需要改一下102 * 先实现删除最大值最小值,103 *104 * 删除后-平衡105 */

106 //修改指针关系

107 public voiddeleteMin(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 voiddeleteMax(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,intdata){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(dataroot.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 inttreeHeightRec(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 voidprintTree(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 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

316 System.out.print(space+curroot.data);317

318 if(curroot.lchild!=null){319 queue.add(curroot.lchild);320 }321 else

322 queue.add(newNode());323 length++;324 if(curroot.rchild!=null){325 queue.add(curroot.rchild);326 }327 else

328 queue.add(newNode());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 voidinOrder(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.html353 *①先判断失衡,及执行那种平衡操作354 * ②平衡操作。左旋,右旋,先左再右旋,先右旋再左旋355 *356 */

357

358 publicNode 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 returnnodeB;378 }379 }380

381 publicNode 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 returnnodeB;401 }402 }403

404 /**

405 * 求高度差,函数406 */

407 public intheightDifference(Node root){408 return treeHeightRec(root.lchild)-treeHeightRec(root.rchild);409 }410

411 //调用平衡

412 public voidbanlanceTree(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 voidmain(String args[]) {445 AVLTree t=newAVLTree();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 }

View Code

结语,虽然我的树状打印函数写的很捞,>4层之后误差较大,肉眼可见,但是调试的时候还是帮了大忙

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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个space5 *6 * 当层数多的时候稍乱,2333不想弄了,7 */

8

9 /**辅助函数-从前一篇复制10 * 求树高11 * 打印树12 */

13 //递归求树高-用于打印树14 //递归

15 public inttreeHeightRec(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 voidprintTree(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 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

49 System.out.print(space+curroot.data);50

51 if(curroot.lchild!=null){52 queue.add(curroot.lchild);53 }54 else

55 queue.add(newNode());56 length++;57 if(curroot.rchild!=null){58 queue.add(curroot.rchild);59 }60 else

61 queue.add(newNode());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

6899f642cc10af1a0e5073917593ad07.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值