平衡是一种非常微妙的属性;你认为你知道它是什么,但很容易出错。特别是,即使Eric Lippert的(好的)答案也有偏差。那是因为
高度的概念是不够的。你需要有一棵树的最小和最大高度的概念(其中最小高度是从根到叶子节点的最少步数,而最大高度是......好吧,你明白了)。在此基础上,我们可以定义平衡为:
任何分支的最大高度不超过任何分支的最小高度一。
(这实际上意味着分支本身也是平衡的;你可以选择相同的分支作为最大和最小值。)
您只需要执行一个简单的树遍历来验证此属性,并跟踪当前深度。第一次回溯时,这会给您一个基准深度。之后每次回溯时,您将新深度与基准深度进行比较:
- 如果它等于基准深度,则继续
- 如果相差超过一,则树不平衡
- 如果相差一,则现在您知道平衡的范围,并且所有后续深度(当您即将回溯时)必须是第一个或第二个值。
代码如下:
class Tree {
Tree left, right;
static interface Observer {
public void before();
public void after();
public boolean end();
}
static boolean traverse(Tree t, Observer o) {
if (t == null) {
return o.end();
} else {
o.before();
try {
if (traverse(left, o))
return traverse(right, o);
return false;
} finally {
o.after();
}
}
}
boolean balanced() {
final Integer[] heights = new Integer[2];
return traverse(this, new Observer() {
int h;
public void before() { h++; }
public void after() { h--; }
public boolean end() {
if (heights[0] == null) {
heights[0] = h;
} else if (Math.abs(heights[0] - h) > 1) {
return false;
} else if (heights[0] != h) {
if (heights[1] == null) {
heights[1] = h;
} else if (heights[1] != h) {
return false;
}
}
return true;
}
});
}
}
我想你可以不使用观察者模式来完成这个任务,但是我发现使用观察者模式更容易理解。
[编辑]: 为什么不能只考虑每一侧的高度呢?考虑下面这棵树:
/\
/ \
/ \
/ \_____
/\ / \_
/ \ / / \
/\ C /\ / \
/ \ / \ /\ /\
A B D E F G H J
好的,有点混乱,但根的两侧是平衡的: C
的深度为2, A
, B
, D
, E
的深度为3,F
, G
, H
, J
的深度为4。左侧分支的高度为2 (记住,随着遍历分支,高度会减少),右侧分支的高度为3。然而,整个树并不平衡,因为 C
和 F
之间的高度差为2。您需要一个极小值-极大值规范(虽然实际算法可以更简单,因为只应该允许两个高度)。