本文参考海纳的两篇文章,需要补平衡二叉树知识的请看这里。

参照的文章是这篇文章。

可以直接去看这两篇文章,再回头看我这篇文章,所以我就去繁就简。

代码

package com.yubotao;/*** @Auther: yubt* @Description:* @Date: Created in 9:23 2018/6/8* @Modified By:*/
public class AVLNode {public int data;public int depth;public int balance;public AVLNode parent;public AVLNode left;public AVLNode right;public AVLNode(int data){this.data = data;depth = 1;balance = 0;left = null;right = null;}public void insert(AVLNode root, int data){if (data < root.data){if (root.left != null){insert(root.left, data);}else {root.left = new AVLNode(data);root.left.parent = root;}}else {if (root.right != null){insert(root.right, data);}else {root.right = new AVLNode(data);root.right.parent = root;}}// 从插入的过程回溯回来的时候,计算平衡因子root.balance = calcBalance(root);// 左子树高,应该右旋if (root.balance >= 2){// 右孙高,先左旋if (root.left.balance == -1){left_rotate(root.left);}right_rotate(root);}// 右子树高,左旋if (root.balance <= -2){// 左孙高,先右旋if (root.right.balance == 1){right_rotate(root.right);}left_rotate(root);}root.balance = calcBalance(root);root.depth = calcDepth(root);}// 右旋private void right_rotate(AVLNode p){// 一次旋转涉及到的结点包括祖父,父亲,右儿子AVLNode pParent = p.parent;AVLNode pLeftSon = p.left;AVLNode pRightGrandSon = pLeftSon.right;// 左子变父pLeftSon.parent = pParent;if (pParent != null){if (p == pParent.left){pParent.left = pLeftSon;}else if (p == pParent.right){pParent.right = pLeftSon;}}pLeftSon.right = p;p.parent = pLeftSon;// 右孙变左孙p.left = pRightGrandSon;if (pRightGrandSon != null){pRightGrandSon.parent = p;}p.depth = calcDepth(p);p.balance = calcBalance(p);pLeftSon.depth = calcDepth(pLeftSon);pLeftSon.balance = calcBalance(pLeftSon);}private void left_rotate(AVLNode p){// 一次选择涉及到的结点包括祖父,父亲,左儿子AVLNode pParent = p.parent;AVLNode pRightSon = p.right;AVLNode pLeftGrandSon = pRightSon.left;// 右子变父pRightSon.parent = pParent;if (pParent != null){if (p == pParent.right){pParent.right = pRightSon;}else if (p == pParent.left){pParent.left = pRightSon;}}pRightSon.left = p;p.parent = pRightSon;// 左孙变右孙p.right = pLeftGrandSon;if (pLeftGrandSon != null){pLeftGrandSon.parent = p;}p.depth = calcDepth(p);p.balance = calcBalance(p);pRightSon.depth = calcDepth(pRightSon);pRightSon.balance = calcBalance(pRightSon);}public int calcBalance(AVLNode p){int left_depth;int right_depth;if (p.left != null){left_depth = p.left.depth;}else {left_depth = 0;}if (p.right != null){right_depth = p.right.depth;}else {right_depth = 0;}return left_depth - right_depth;}public int calcDepth(AVLNode p){int depth = 0;if (p.left != null){depth = p.left.depth;}if (p.right != null && depth < p.right.depth){depth = p.right.depth;}depth++;return depth;}}

这里阐述几个概念:

平衡因子:该结点左右子树的深度(高度)差。


深度(高度):树的层数。

如图

四棵树的深度依次为:4,3,4,3.

这两个概念理解好才能更加快速的理解上面的代码。

插入原理讲解

我们先看第一种情况:

c节点也就是黄色节点是我们新插入的节点,此时树不平衡,需要进行操作。根据各节点平衡因子判断(可参照代码),我们需要先右旋后左旋。这里已经把每个节点都做了相应的标记了,相关步骤就是代码的执行顺序。

我只做一次代码解读,后面的自己看图对应一下。

首先,我们进入insert()方法,此时应该是新建一个节点c。执行到方法体中的开头部分

if (data < root.data){if (root.left != null){insert(root.left, data);}else {root.left = new AVLNode(data);root.left.parent = root;}}

此时走else分支,新建了一个结点,并指定c的parent结点。
然后我们这时需要执行到这步:

// 从插入的过程回溯回来的时候,计算平衡因子root.balance = calcBalance(root);

计算b结点的balance,为1。后续两个if判断跳过,并计算了b结点的balance为1,depth为2.

此时怎么样了呢?

我们从a结点的insert()方法中的开头部分

else {if (root.right != null){insert(root.right, data);}else {root.right = new AVLNode(data);root.right.parent = root;}}

if分支跳出(因为我们插入的时候,a有右子树b,所以当时进入了b结点的insert()方法)。
接下来计算a结点的balance,为-2,进入第二个if分支

 // 右子树高,左旋if (root.balance <= -2){// 左孙高,先右旋if (root.right.balance == 1){right_rotate(root.right);}left_rotate(root);}

并且b结点的balance为1,符合条件,开始进行右旋操作。进入方法right_rotate()中。
这时p为b结点,所以就如上图标识,按照代码顺序执行此次右旋操作。

// 右旋private void right_rotate(AVLNode p){// 一次旋转涉及到的结点包括祖父,父亲,右儿子AVLNode pParent = p.parent;AVLNode pLeftSon = p.left;AVLNode pRightGrandSon = pLeftSon.right;// 左子变父pLeftSon.parent = pParent; // 图中左1if (pParent != null){if (p == pParent.left){pParent.left = pLeftSon;  // 图中左2}else if (p == pParent.right){pParent.right = pLeftSon; }}pLeftSon.right = p; // 图中左3p.parent = pLeftSon;  // 图中左4// 右孙变左孙p.left = pRightGrandSon;  // 图中左5if (pRightGrandSon != null){pRightGrandSon.parent = p;}p.depth = calcDepth(p);p.balance = calcBalance(p);pLeftSon.depth = calcDepth(pLeftSon);pLeftSon.balance = calcBalance(pLeftSon);}

接下来进入左旋方法left_rotate(),注意此时的p结点为a结点。
如上图右边标识,再看一下代码执行顺序:

private void left_rotate(AVLNode p){// 一次选择涉及到的结点包括祖父,父亲,左儿子AVLNode pParent = p.parent;AVLNode pRightSon = p.right;AVLNode pLeftGrandSon = pRightSon.left;// 右子变父pRightSon.parent = pParent; // 图中右1if (pParent != null){if (p == pParent.right){pParent.right = pRightSon;}else if (p == pParent.left){pParent.left = pRightSon;}}pRightSon.left = p;  // 图中右2p.parent = pRightSon;  // 图中右3// 左孙变右孙p.right = pLeftGrandSon;  // 图中右4if (pLeftGrandSon != null){pLeftGrandSon.parent = p;}p.depth = calcDepth(p);p.balance = calcBalance(p);pRightSon.depth = calcDepth(pRightSon);pRightSon.balance = calcBalance(pRightSon);}

此时,完成整个插入过程。

接下来我们再看两种情况。

可以看到圈出的两部分就是上面两种情况。所以就不一一赘述。

最后再看更复杂的两种情况,只贴图,可以自己跟着代码顺序捋一捋,熟悉一下这个过程,我就不一一分析了。

删除方法

这部分待续,我还没完全想好,抽个时间再来更新吧。
主要删除的时候需要考虑,如何处理待删结点有子节点的情况。
如果待删结点是叶子结点,那么很容易删除,然后向上回溯,看一下各结点的平衡因子,并执行相关的左右旋方法即可。
但是如果待删结点有左右子树时,这个时候就要考虑如何操作了,怎样将该结点删除又不影响其子节点。
我的一个想法是:将待删结点变为叶子结点,这样就好操作了,不过这个代码怎么写,如果同时有左右结点,和哪个结点互换,是不是都行呢?
有个想法是如果待删结点有左右子树,将该结点与中序遍历的前一个结点交换,如果其还有左右子树,再交换,直到结点无左右子树为止(递归),然后删除,在向上回溯,判断其平衡因子。

先思考到这里,有兴趣的朋友可以留言,一起讨论。

终于抽时间把删除的代码写完了,调试了很久,想的时候思路挺清晰的,但是一旦写的时候就能看出之前考虑的很多地方都不是很周全。

大概耗时2个多小时。先贴一下代码

public void delete(AVLNode root, int data){if (data < root.data)delete(root.left, data);else if (data > root.data)delete(root.right, data);else{if (root.left != null)exchange(root, root.left);else if (root.right != null)exchange(root, root.right);// 当data节点为子节点时if (data == root.data && root.left == null && root.right == null){if (root.parent.left.data == root.data){root.parent.left = null;root.parent = null;}else if (root.parent.right.data == root.data){root.parent.right = null;root.parent = null;}}}// 回溯,计算平衡因子root.balance = calcBalance(root);// 左子树高,应该右旋if (root.balance >= 2){// 右孙高,先左旋if (root.left.balance == -1){left_rotate(root.left);}right_rotate(root);}// 右子树高,左旋if (root.balance <= -2){// 左孙高,先右旋if (root.right.balance == 1){right_rotate(root.right);}left_rotate(root);}root.balance = calcBalance(root);root.depth = calcDepth(root);}
private void exchange(AVLNode p, AVLNode pSon){int temp;temp = p.data;p.data = pSon.data;pSon.data = temp;// 当data节点不为子节点时if (pSon.left == null && pSon.right == null){if (pSon.parent.left.data == pSon.data){pSon.parent.left = null;pSon.parent = null;}else if (pSon.parent.right.data == pSon.data){pSon.parent.right = null;pSon.parent = null;}// 保证删除子节点后的p的平衡因子和深度正确,这样才能保证整体的正确p.depth = calcDepth(p);p.balance = calcBalance(p);return;}if (pSon.left != null){exchange(pSon, pSon.left);// 回溯时需计算平衡因子及深度,否则会出现错误p.depth = calcDepth(p);p.balance = calcBalance(p);// 保证递归回溯的时候不进行其他分支操作return;}if (pSon.right != null){exchange(pSon, pSon.right);p.depth = calcDepth(p);p.balance = calcBalance(p);return;}}

基本的坑位我都给标记出来了。

测试代码:

public class TestAVLNode {public static void main(String[] args) {AVLNode one = new AVLNode(5);AVLNode two = new AVLNode(3);AVLNode three = new AVLNode(7);AVLNode four = new AVLNode(2);AVLNode five = new AVLNode(4);AVLNode six = new AVLNode(6);AVLNode seven = new AVLNode(1);one.left = two;one.right = three;two.parent = one;three.parent = one;two.left = four;two.right = five;four.parent = two;five.parent = two;three.left = six;six.parent = three;four.left = seven;seven.parent = four;one.balance = 1;one.depth = 4;two.balance = 1;two.depth = 3;three.balance = 1;three.depth = 2;four.balance = 1;four.depth = 2;five.balance = 0;five.depth = 1;six.balance = 0;six.depth = 1;seven.balance = 0;seven.depth = 1;System.out.println(one);one.delete(one, 7);System.out.println(two);}
}

测试树长这样

说一下整个过程碰到的几个问题。

首先是交换值方法。因为被删节点如果不是子节点,就需要把它交换到子节点位置,然后再删除。所以为了写这个交换方法,我先写了一个单链表的交换,比较简单:

package com.yubotao;/*** @Auther: yubt* @Description:* @Date: Created in 18:50 2018/6/21* @Modified By:*/
public class TestExchange {private static class One{private One next;private int data;public One getNext() {return next;}public void setNext(One next) {this.next = next;}public int getData() {return data;}public void setData(int data) {this.data = data;}@Overridepublic String toString() {return "One{" +"data=" + data +'}';}}public static void main(String[] args) {One one = new One();One two = new One();One three = new One();One four = new One();one.setData(1);one.setNext(two);two.setData(2);two.setNext(three);three.setData(3);three.setNext(four);four.setData(4);System.out.println("one: " + one + "," + "two: " + two + "," + "three: " + three + "," + "four: " + four + ".");exchange(one, two);System.out.println("one: " + one + "," + "two: " + two + "," + "three: " + three + "," + "four: " + four + ".");}// 死循环static void exchange(One root, One next){int temp;temp = root.data;root.data = next.data;next.data = temp;while (next.next == null){return;}exchange(next,next.next);}
}

可以看到我标注了死循环,这就是踩得第一个坑,没有很好的找到递归退出的条件,后来就用了最简单的条件

while (next.next == null){return;}

开始时想的退出条件有一些问题,当时写的是:

static void exchange(One root, One next){while (next.next != null){exchange(next,next.next);}int temp;temp = root.data;root.data = next.data;next.data = temp;}

对,你没有看错! 这个没有退出条件,刚才我试了一下,即使加上退出条件还是有问题的。

static void exchange(One root, One next){while (next.next != null){exchange(next,next.next);}int temp;temp = root.data;root.data = next.data;next.data = temp;return;}

主要问题就出在while关键字上,另外数据交换部位也应该在条件判断的前面,否则结果也会不对。
修改成这样

static void exchange(One root, One next){int temp;temp = root.data;root.data = next.data;next.data = temp;if (next.next != null){exchange(next,next.next);}return;}

可以看出已经具备我们想要的方法的雏形了。

既然单链表完成了,应用到二叉树其实就是一个分支判断的问题,所以这块其实比较简单,没什么说的。

接下来的问题是,删除节点应该在哪个位置
最开始我是放到delete方法中的,写的是这样的

else{if (root.left != null)exchange(root, root.left);else if (root.right != null)exchange(root, root.right);// 当data节点为子节点时if (root.left == null && root.right == null){if (root.parent.left.data == root.data){root.parent.left = null;root.parent = null;}else if (root.parent.right.data == root.data){root.parent.right = null;root.parent = null;}}}

这时我们看到exchange中没有相关删除方法,就出现了一个问题,如果待删节点不是子节点,而是交换的节点,那么我们只是把值交换了,并没有删除节点。这里可能一下子有点看不明白,我建议你画下这个exchange的递归图。**注意出口位置!**你就会明白我说的意思了。

打个比方

你在图中的3号节点进入exchange方法,最后3号换到了1号位置。这时退出了exchange方法,**注意!**这时的delete方法中的root节点现在值为2,但还是原3号节点!故不会删除原值为1,现值为3那个子节点。

那么我觉得删除节点应该放到exchange中来做,这样不就ok了嘛。
但是!如果data值为子节点,是不会执行exchange方法的!所以最后得出结论,无论是exchange还是delete方法,都需要删除节点的操作,但是需要区分一下情况。
delete方法中的是删除data值就为子节点的。

            // 当data节点为子节点时if (data == root.data && root.left == null && root.right == null){if (root.parent.left.data == root.data){root.parent.left = null;root.parent = null;}else if (root.parent.right.data == root.data){root.parent.right = null;root.parent = null;}}

注意!data == root.data这个条件不可或缺,这里我不点出来,你可以测试一下,没有这个值的时候,有什么问题。
提醒,方法的执行顺序。

最后的问题就是平衡因子和深度总是出问题,以及递归回溯的过程中出现的一些怪相。
比如这段代码就把这块的问题概括了。

if (pSon.left != null){exchange(pSon, pSon.left);// 回溯时需计算平衡因子及深度,否则会出现错误p.depth = calcDepth(p);p.balance = calcBalance(p);// 保证递归回溯的时候不进行其他分支操作return;
}

首先是return;,它很关键,开始的时候我没有写这个,就会出现这样的情况,递归回溯的时候,继续走下一个条件判断,导致本来从左子树回溯上来的,又和右子树做了一次exchange。所以必须加上return;

之后是

p.depth = calcDepth(p);
p.balance = calcBalance(p);

这两句计算需要在每次回溯的时候重新计算。
一开始的时候,我认为只要最下面一个子树的平衡因子和深度正确,那么在delete方法回溯的时候,其他的也会计算正确。但是我忘记了exchange方法回溯的时候也需要重新计算的,否则只要有一个节点有问题,就会影响其上的祖先节点出现问题!

大概到这里也就总结的差不多了。可能我写的方法不够优雅,简洁,效率可能也很有问题,但是毕竟是自己思考以及一点一点调试写出来的,我觉得我应该鼓励一下自己。

我是真滴流批!

哈哈哈~

java实现平衡二叉树相关推荐

  1. 使用Java实现平衡二叉树

    使用Java实现平衡二叉树 二叉树是一种较为复杂的数据结构,二叉树算法在数据查询时的时间复杂度为 O(log n)(n为保存元素个数). 但是普通的二叉树在数据添加和删除时很容易出现树结构不平衡问题 ...

  2. java实现平衡二叉树(详细分析)

    package com.utils; import java.util.Iterator; import java.util.NoSuchElementException; /*** 平衡二叉树* 定 ...

  3. Java判断平衡二叉树

    输入一棵二叉树的根节点,判断该树是不是平衡二叉树.如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树. /*** Definition for a binary tree n ...

  4. 带你彻底弄明白!java实现平衡二叉树

    一面: 先是问了问项目,然后就开始问一些问题 1.每个请求耗时100ms,机器的配置是4核8G,问要达到10000TPS需要多少台机器? 没答上来,问了问是IO密集型还是CPU密集型,然后面试官说我想 ...

  5. 用Java实现平衡二叉树

    ​平衡二叉树的定义 在谈平衡二叉树之前,首先了解一下二叉排序树.空树或者具有以下特性的二叉树就是二叉排序树: 若左子树非空,则左子树上所有结点关键字的值均小于根节点的关键字的值. 若右子树非空,则右子 ...

  6. Java数据结构——平衡二叉树(AVL树)

    AVL树的引入 搜索二叉树有着极高的搜索效率,但是搜索二叉树会出现以下极端情况: 这样的二叉树搜索效率甚至比链表还低.在搜索二叉树基础上出现的平衡二叉树(AVL树)就解决了这样的问题.当平衡二叉树(A ...

  7. Java 求解平衡二叉树

    文章目录 一.题目 二.题目分析 三.迭代法 四.总结 一.题目 给定一个二叉树,判断它是否是高度平衡的二叉树. 本题中,一棵高度平衡二叉树定义为: 一个二叉树每个节点 的左右两个子树的高度差的绝对值 ...

  8. Java 判断平衡二叉树

    /*** @author 陈明勇*/ public class AvlTree {private Boolean isAvlTree(TreeNode treeNode) {if (treeNode ...

  9. java循环遍历类属性_java循环遍历类属性 get 和set值方法

    //遍历sqspb类 成员为String类型 属性为空的全部替换为"/" Field[] fields = sqspb.getClass().getDeclaredFields() ...

最新文章

  1. 消息幂等(去重)通用解决方案,真顶!
  2. PCL中PFH、FPFH理论
  3. 最大流ISAP算法模板
  4. DigSci科学数据挖掘大赛-亚军方案分享
  5. 如何处理VirtualBox启动错误消息:The vboxdrv kernel module is not loaded
  6. 怎么打包图片_怎么将许多张照片打包发到邮箱?
  7. 三台云服务器搭建hadoop集群
  8. 带有.NET Core 3和Electron.NET的多平台桌面HTML编辑器
  9. idea调试代码步入用法
  10. python json解析工具选择_推荐几个开发必备的JSON工具
  11. 员工考勤管理系统c语言,员工考勤信息管理小程序,考勤信息管理小程序
  12. Unity3D 发布APK安卓环境配置步骤、安装、教程(含Java/Android)(超全流程)
  13. 博途v14电脑要求_博图TIA V14版本完整体验加测试
  14. msxml3.dll 错误 '80072efd' A connection with the server could not be established
  15. 云原生时代,Kubernetes 让应用落地的 N 种招式(附 PPT)
  16. 软考-高项-论文-信息系统项目的人力资源管理
  17. 聊聊MySQL存储过程
  18. python爬取bili指定up主的视频
  19. 论文阅读笔记(4)——《Language Generation with Multi-Hop Reasoning on Commonsense Knowledge Graph》
  20. 菱形程序设计以及宏定义的应用

热门文章

  1. Java方向发展前景与就业方向探讨
  2. INSERT DESC UPDATE SELECT
  3. 基于扫描的磁盘调度算法
  4. vue项目如何区分开发、生产和测试环境
  5. 云端原生数据泄露事件解析
  6. UI设计中色彩搭配使用技巧
  7. 亿级流量电商详情页系统的大型高并发与高可用缓存架构实战 目录
  8. grid布局浏览器兼容_CSS Grid 网格布局教程
  9. Unity------Bounds
  10. Javascript——js常用的方法(一)...........