实现二叉树对于我们已经算是轻车熟路了。先来定义树的结点:

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;
}
}
按照上一篇文章的分析,我们需要在结点里定义代表当前子树深度的 depth,以及表示左右子树高度之差的 balance。其他的则与普通的二叉树看上去并没有什么区别。
插入的过程,前半部分也和普通的二叉树没有什么区别:
private 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);
将这一部分代码合并到 insert 方法中去,就可以得到完整的实现了。至于这里面定义的右旋,其过程,我们上一节也已经讲解过了。这里只要实现就可以了。
private void right_rotate(AVLNode p) {
/* 一次旋转涉及到的结点包括祖父,父亲,右儿子 */
AVLNode pParent = p.parent, pRightSon = p.left;
AVLNode pLeftGrandSon = pRightSon.right;
/* 左子僭为父 */
pRightSon.parent = pParent;
if (pParent != null) {
if (p == pParent.left)
pParent.left = pRightSon;
else if (p == pParent.right)
pParent.right = pRightSon;
}
pRightSon.right = p;
p.parent = pRightSon;
/* 右孙变左孙 */
p.left = pLeftGrandSon;
if (pLeftGrandSon != null)
pLeftGrandSon.parent = p;
/* 重新计算平衡因子 */
p.depth = calcDepth(p);
p.balance = calcBalance(p);
pRightSon.depth = calcDepth(pRightSon);
pRightSon.balance = calcBalance(pRightSon);
}
左旋代码就不再给出了,请读者自行补充。
另外,还有计算平衡度的代码:
private 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;
}
private 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;
}

这样,一个完整的平衡二叉树才算完成了。

今天的作业:

1. 补充左旋代码。

2. 平衡二叉树的删除操作与插入操作很相似。在真正地删除一个结点以后,沿着查找路径向上回溯,并且检查路径上的每一个结点,看它是否平衡,如果不平衡,仍然按照上述规则进行调整。请自己实现平衡二叉树的删除。

课外阅读:

有一道这样的题目,问高度为h的平衡二叉树最少有多少个结点。 解答这个题目,可以从树的构造出发,如果我们能构造出具有最少结点的高度为h的平衡二叉树,就相当于解决了这道题目。先从最结点数目比较少的情况出发。容易想象,当高度为1时,最少的结点个数是1,我们记这种树为T1,当高度为2时,最少的结点个数为2,记 这种树为T2,并且,我们知道,T2有两种形态,分别是只有一个左孩子结点和只有一个右孩 子结点。要构造有3层的最少结点的平衡二叉树T3,只需要新增一个根结点 root ,令root的 左子树为T1,右子树为T2。构造4层的平衡二叉树时,同样地,新增一个根结点,然后令左 子树为T2,右子树为T3,依次类推。再记Tn的结点个数为Fn,就可以得到公式:

然后再得到通项公式:

对上式求逆函数,可以得到有n个结点的平衡二叉树最多有多少层。

平衡二叉树的插入,删除操作都仅和树的高度h有关,它们的时间复杂度是O(h)的。(如何得到这个通项公式,先不用管,这个是组合数学里的内容。很多计算机专业的本科阶段的课程都还没有涉及。我们这里先记住结论,以后如果有机会的话,我可能会介绍一部分组合数学的知识)