二叉平衡树的定义:
具有下列性质的二叉查找树
:它的左右子树都是平衡二叉树,且左右子树的高度之差不能的绝对值不能超过1。
将二叉树结点的平衡因子
定义为:该结点的左子树的高度减去右子树的高度,那么所有结点的平衡因子只能是-1、0、1。
1. 二叉平衡树的判定算法
思路:得到左右子树的高度,然后判断该结点的平衡因子是否是-1、0或1。如果不是,则直接返回false;如果是,则递归遍历左右子树。
算法一:
public static int treeDepth(BinaryTreeNode root) {
if (root == null) {
return 0;
}
int leftDepth = treeDepth(root.left);
int rightDepth = treeDepth(root.right);
// +1 是加上根结点的深度1。
return 1+(leftDepth >rightDepth? leftDepth : rightDepth );
}
public boolean isBalancedTree(BinaryTreeNode root){
if (root==null){
return true;//空树
}
int leftDepth = treeDepth(root.leftChild);
int rightDepth = treeDepth(root.rightChild);
int diff = leftDepth - rightDepth;
if (diff > 1 || diff < -1) {
return false; //若左右子树深度差超过1,则终止遍历,此树不为平衡二叉树
}
return isBalancedTree(root.leftChild) && isBalancedTree(root.leftChild); //继续递归遍历其左右子树
}
虽然这个算法简单明了,但是它却不是最好的,因为:当你获取一个结点的左右子树的深度时,其实已经遍历了一遍它的左右子树。而上面的算法判断完这个结点的平衡因子符合要求之后,又去递归遍历该结点的左右子树,会导致一个结点会遍历多次。
改进思路:当从上往下遍历二叉树时,就会使一个结点遍历多次。那么我们可以从下往上遍历(左-右-根:后序遍历)。那么在遍历到一个结点前,其左右子树都已经被遍历过了,我们自然也就能够拿到这个结点左右子树的深度进行判断从而避免两层递归嵌套。
先看下面的算法(该算法是错误的,错误原因等一下分析):
public boolean isBalancedTree(BinaryTreeNode root){
int depth=0;
boolean isBa=isBalancedHelper(root,depth);
System.out.println("depth: "+depth);
return isBa;
}
private boolean isBalancedHelper(BinaryTreeNode root, int depth) {
if (root == null) {
depth = 0;
return true;
}
int leftDepth=0;
int rightDepth=0;
if (isBalancedHelper(root.leftChild, leftDepth)
&& isBalancedHelper(root.rightChild, rightDepth)) {
int diff = leftDepth - rightDepth;
if (diff >= -1 && diff <= 1) {
depth = 1 + (leftDepth > rightDepth ? leftDepth : rightDepth);
return true;
}
}
return false;
}
在这个算法中,一开始我一直在想:leftDepth初始化之后在哪更新深度呢?
后来才想到isBalancedInner(root.leftChild, leftDepth)
把leftDepth作为参数传入递归中,所以leftDepth的更新本质上是在:depth = 1 + (leftDepth > rightDepth ? leftDepth : rightDepth);
这里的depth就是递归中的leftDepth,如此一来就完成了对leftDepth的更新深度。rightDepth同理。
讲到这里,我们再来看一下例子:
public static void main(String[] args) {
int a=5;
changeA(a);
System.out.println("after changeB a: "+a);
System.out.println("----------------------");
Integer b=new Integer(5);
changeB(b);
System.out.println("after changeB b: "+b);
System.out.println("----------------------");
int[] c=new int[1];
c[0]=5;
changeC(c);
System.out.println("after changeC c[0]: "+c[0]);
}
static void changeA(int a){
a=100;
System.out.println("in changeA a: "+a);
}
static void changeB(Integer b){
b=100;
System.out.println("in changeB b: "+b);
}
static void changeC(int[] c){
c[0]=100;
System.out.println("in changeB c[0]: "+c[0]);
}
//log
in changeA a: 100
after changeB a: 5
----------------------
in changeB b: 100
after changeB b: 5
----------------------
in changeB c[0]: 100
after changeC c[0]: 100
从这段例子可以看出,参数传递数值时,并不会影响原来的数值。当传递引用,通过引用去改变数值时,才会改变原来的数值。
我们再回到刚刚的平衡树判定算法:
int leftDepth=0;
int rightDepth=0;
if (isBalancedInner(root.leftChild, leftDepth)
&& isBalancedInner(root.rightChild, rightDepth)) {
int diff = leftDepth - rightDepth;
if (diff >= -1 && diff <= 1) {
depth = 1 + (leftDepth > rightDepth ? leftDepth : rightDepth);
return true;
}
}
可以发现递归调用时,是传递的数值。所以递归方法中depth改变,返回上一层时原来的depth并不会改变。而我们从下往上遍历时,左右子树的深度会影响到根结点的平衡因子,所以我们应该在递归中真实改变
传入的参数,这样左右子树的深度值才会保留用来计算父结点的平衡因子与深度。
综上所述,正确的算法如下:
public static boolean isBalancedTree(BinaryTreeNode root){
int[] depth=new int[1];
boolean isBa=isBalancedHelper(root,depth);
System.out.println("depth: "+depth[0]);
return isBa;
}
private static boolean isBalancedHelper(BinaryTreeNode root,int[] depth){
if (root == null) {
depth[0] = 0;
return true;
}
int[] leftDepth=new int[1];
int[] rightDepth=new int[1];
if (isBalancedHelper(root.leftChild, leftDepth)
&& isBalancedHelper(root.rightChild, rightDepth)) {
int diff = leftDepth[0] - rightDepth[0];
if (diff >= -1 && diff <= 1) {
depth[0] = 1 + (leftDepth[0] > rightDepth[0] ? leftDepth[0] : rightDepth[0]);
return true;
}
}
return false;
}
2. 二叉平衡树的旋转
二叉平衡树的旋转并不是剑指Offer的算法,就当是一个扩展吧。
插入或删除结点时,可能会使二叉平衡树失衡,需要通过旋转来保证平衡。
数据结构:关于AVL树的平衡旋转详解
网友评论