这完全取决于您对 (1) 树和 (2) 高度的定义。但是我们当然希望保持高度是从树到整数的总函数的性质;不应该有未定义高度的树。
假设我们有一个二叉树的定义:
一棵树被定义为 (1) 空树,或 (2) 一对树,称为左子树和右子树。
type t = Empty | Node of t * t
现在我们可以定义高度,它应该是一个总函数:一棵空树的高度为零——还能是什么? -- 非空树的高度是子树高度中较大者加一:
let max x y = if x > y then x else y
let rec height tree = match tree with
| Empty -> 0
| Node (left, right) -> 1 + max (height left) (height right)
现在,请注意将我们带到这里的逻辑链:
- 高度是一个总函数
- empty 是合法树
- 因此,一棵空树必须有高度
- 空树的唯一合理高度为零
- 因此,具有单个节点的树的高度必须为 1。
如果我们否认其中一些前提,那么我们可以提出其他答案。例如,如果没有空树怎么办?
一棵树被定义为一个树列表,可能是空的:
type t = Node of t list
我们可以再次定义高度:具有空列表的节点的高度定义为零,具有非空子节点的节点的高度定义为最大子节点高度加一。
let max x y = if x > y then x else y
let rec height tree = match tree with
| Node [] -> 0
| Node h :: t -> max (1 + height h) (height (Node t))
在这个定义中,具有单个节点的树的高度为零,我们正在计算边数。再次,看看我们的推理:
- 高度是一个总函数
- 空树不是合法树,但叶子是合法树
- 因此叶子必须有高度
- 叶子的合理高度为零
- 因此,单叶树的高度可能为零。
但是我们可以也说叶子的高度是1,否则定义相同,我们会计算节点。逻辑上没有异议。
使用节点计数和其他时间边计数的情况是什么?
如果空树是合法的,那么显然只有节点数才有意义。如果我们尝试计算边数,则无法区分空树的高度和单节点树的高度,并保持高度为总函数。
如果一棵空树不合法,那么两者都有意义。由于两个高度函数之间的关系是“它们相差一个”,所以使用哪个定义并不重要;如果您想使用其他定义,只需适当地添加或减去一个。
平衡一棵树时,我们不关心绝对高度;我们关心两棵树之间的高度差异。在那些算法中,我们是否计算边或节点是无关紧要的。无论如何,差异将是相同的。很多时候这并不重要,所以选择你更喜欢的那个。