要求:判断一颗二叉树是否平衡。
平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
1 一般递归
TreeNode.java
package BinaryTree;
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
public TreeNode(TreeNode l, TreeNode r, int v) {
left = l;
right = r;
val = v;
}
public TreeNode(int v) {
val = v;
}
}
【核心】BinaryTreeTest.java
public class BinaryTreeTest {
private int getDepth(TreeNode t) {
if (t == null) {
return 0;
}
int leftDepth = getDepth(t.left);
int rightDepth = getDepth(t.right);
return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
public boolean isBalanced(TreeNode t) {
// 空树是平衡二叉树
if (t == null) {
return true;
}
// 判断根节点的平衡因子是否大于1 或者 小于-1
int leftDepth = getDepth(t.left);
int rightDepth = getDepth(t.right);
int balanceFactor = leftDepth - rightDepth;
if (balanceFactor > 1 || balanceFactor < -1) {
return false;
}
// 如果跟节点的平衡因子正常,则判断它的左右子树是否为平衡二叉树
else {
return isBalanced(t.left) && isBalanced(t.right);
}
}
}
测试
package BinaryTree;
public class Main {
public static void main(String[] args) {
/***
* 新建一个二叉树:
*
* 15
* / \
* 12 25
* / \
* 9 13
* ***/
TreeNode root = new TreeNode(15);
TreeNode l = new TreeNode(12);
root.left = l;
TreeNode r = new TreeNode(25);
root.right = r;
TreeNode ll = new TreeNode(9);
TreeNode lr = new TreeNode(13);
l.left = ll;
l.right = lr;
BinaryTreeTest bTreeTest = new BinaryTreeTest();
Boolean ret = bTreeTest.isBalanced(root);
System.out.println(ret.toString());
//在 9 的右边再添加一个节点 10,再判断这颗树是否平衡
ll.right = new TreeNode(10);
ret = bTreeTest.isBalanced(root);
System.out.println(ret.toString());
}
}
output:
true
false
该方法的问题在于,getDepth方法已经是在递归,每次判断一个节点的深度就遍历了一遍子树,而在判断是否平衡的时候又要遍历一编整个树,所以,上面的一般递归的算法存在多次遍历的问题,树的结构一旦比较大就不能使用这种方法了。
2 一次遍历的递归算法
目标:我们希望对整个树进行一次遍历就得到我们想要的结果。
这个目标可以实现吗? 我们来分析一下。首先,对于判断一颗二叉树是否平衡的问题的核心其实就是要获得所有节点的深度。上面的getDepth算法就是每次都从叶子节点开始往根节点回滚,从而得到根节点的深度,实际上在这个过程中,所以的节点的深度都计算了一遍,所以,我们的目标是可行的,而且方法就是从叶子节点往上遍历。
那么,怎么样才能做到从叶子往上遍历呢?我们不妨想想【后序遍历】是不是就是这样?每次都是先访问完左右子树才访问根节点的!所以,这里我们就是要利用这种方法,一边遍历,一边计算每个节点是不是平衡的。
/**
* isBalancedImproved的包装方法
**/
public boolean isBalancedImproved(TreeNode t) {
return isBalancedImproved(t, -1);
}
/**
* t: 子树的根节点 depth: 子树的深度。这个参数是用来向上层传递这一节点的计算结果的
**/
private boolean isBalancedImproved(TreeNode t, int depth) {
if (t == null) {
depth = 0;
return true;
}
int leftDepth = -1;
int rightDepth = -1;
int balanceFactor;
// 如果 左子树 和 右子树 都是平衡的?
if (isBalancedImproved(t.left, leftDepth) && isBalancedImproved(t.right, rightDepth)) {
// 判断根节点的平衡因子
balanceFactor = leftDepth - rightDepth;
if (balanceFactor > 1 || balanceFactor < -1) {
// 只要出现不平衡的子树就不要继续往上判断了
return false;
} else {
// 如果两个子树都为平衡的,则要继续往上层判断
depth = leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
return true;
}
} else {
return false;
}
}
将上面的测试代码略做修改之后进行测试:
BinaryTreeTest bTreeTest = new BinaryTreeTest();
Boolean ret = bTreeTest.isBalancedImproved(root);
System.out.println(ret.toString());
//在 9 的右边再添加一个节点 10,再判断这颗树是否平衡
ll.right = new TreeNode(10);
ret = bTreeTest.isBalancedImproved(root);
System.out.println(ret.toString());
结果:
true
true
这明星是错的啊!!!!!!!!!!!!
博主经过检查,发现了一个常见问题,那就是java的方法参数传递的时候全部都是clone变量,也就是深复制,所 isBalancedImproved(TreeNode t, int depth) 方法并不难像我们想象的那样可能通过参数 depth 将计算结果传递到上一层。结合代码说就是:
…………
int leftDepth = -1;
int rightDepth = -1;
int balanceFactor;
// 如果 左子树 和 右子树 都是平衡的?
if (isBalancedImproved(t.left, leftDepth) && isBalancedImproved(t.right, rightDepth)) {
…………
在进行递归之后,这里的leftDepth还是-1。
解决方案
将第二个参数改为一个引用型变量:类对象。所以我们新建一个内部辅助类AuxClass. (在复习的时候觉得我之前po出来的代码逻辑和注释还不够清楚,所以我这里重新写了一份代码,所以和上面的有一点点出入,但是思路一样,懒得改了。)
package Depth;
public class Test {
public boolean isBalanced(TreeNode root) {
return isBalanced(root, new AuxClass());
}
private boolean isBalanced(TreeNode root, AuxClass aux) {
// 边界条件: 到达树的底部,下面已经没有节点了
// 空的树 是 平衡树
if (root == null) {
// 叶子节点的高为0, 设置aux为0,返回给上一层递归
aux.height = 0;
return true;
}
// 判断一颗树是否平衡,需要知道它左右两颗子树的高度,然后根据高度差来判断
// 先去判断左子树的情况
AuxClass leftHeight = new AuxClass(); // 用于保存左子树的高,并不是一个参数,所以初始化为-1
boolean leftResult = isBalanced(root.left, leftHeight);
// 再判断右子树的情况
AuxClass rightHeight = new AuxClass();
boolean rightResult = isBalanced(root.right, rightHeight);
// 如果子树都不平衡,整课树当然也就是不平衡
if (!leftResult || !rightResult) {
// 这里的return并不代码向上返回就结束了,而是将这个false一层层继续向上返回
// 只是已经返回false了,后面的高度也就不再重要了。因为每次都是先判断子树的平衡性
return false;
} else {
// 即使左右子树都是平衡的,还要判断它的高度差
int balanceFactor = leftHeight.height - rightHeight.height;
if (balanceFactor > 1 || balanceFactor < -1) {
return false;
} else {
// 判断到这里,说明这颗树的子树都平衡,它自身也平衡,所以才有必要向上传递自己的高度,并返回true
aux.height = balanceFactor > 0 ? leftHeight.height + 1 : rightHeight.height + 1;
return true;
}
}
}
/**
* 内部类,辅助类
*/
class AuxClass {
public int height;
public AuxClass() {
height = -1;
}
public AuxClass(int i) {
height = i;
}
}
public static void main(String[] args) {
Test test = new Test();
TreeNode root = new TreeNode(8);
TreeNode l = new TreeNode(6);
TreeNode r = new TreeNode(7);
root.left = l;
root.right = r;
System.out.println("isBalanced? : " + test.isBalanced(root));
TreeNode ll = new TreeNode(5);
l.left = ll;
TreeNode lll = new TreeNode(2);
ll.left = lll;
System.out.println("isBalanced? : " + test.isBalanced(root));
}
}
关于我在注释中提到的【向上返回】 的概念可以参考我的另外一篇讲递归的文章:http://blog.csdn.net/cds86333774/article/details/50907444。