您可以按深度优先后序访问二叉树,并使用偏移量来跟踪您相对于起始节点向左/向右移动了多远。每次向左移动时,偏移量都会减少,每次向右移动时,偏移量都会增加。如果您的访问过程以 的偏移量调用0
,那么这意味着被访问的节点与您的起始节点具有相同的偏移量(即它在同一列中),因此您必须添加它的值。
伪代码:
procedure visit (node n, int offset) {
sumleft = 0
sumright = 0
if (n.left != null)
sumleft = visit(n.left, offset - 1)
if (n.right != null)
sumright = visit(n.right, offset + 1)
if (offset == 0)
return n.value + sumleft + sumright
else
return sumleft + sumright;
}
例如,如果您调用
visit(A, 0)
您将收到以下电话:
visit(A, 0) -> E.value + F.value + A.value
visit(B, -1) -> E.value
visit(D, -2) -> 0
visit(H, -3) -> 0
visit(I, +2) -> 0
visit(E, 0) -> E.value
visit(C, +1) -> F.value
visit(F, 0) -> F.value
visit(G, +1) -> 0
另一个例子,从 node 开始B
:
visit(B, 0)
visit(D, -1)
visit(H, -2)
visit(I, 0) -> here we return I.value
visit(E, +1)
当递归返回到我们的初始调用时visit(B, 0)
,sumleft = I.value
我们sumright = 0
返回了最终结果B.value + I.value
,正如预期的那样。
O(n)的复杂性,因为您访问了一次以起始节点为根的树的所有节点。
在考虑了上述算法之后,我意识到它有一个局限性,当我们考虑一个更复杂的树时,这一点变得很明显,如下所示:
在这种情况下visit(B, 0)
仍会返回B.value + I.value
,但这不是预期的结果,因为N
也在同一列上。下面的算法应该可以解决这个问题:
procedure visit(node n, int c, int t) {
sumleft = 0;
sumright = 0;
if (n.left != null)
sumleft = visit(n.left, c - 1, t)
if (n.right != null)
sumright = visit(n.right, c + 1, t)
if (c == t)
return n.value + sumleft + sumright;
else
return sumleft + sumright;
}
这个想法本质上是相同的,但是我们现在有一个c
给出当前列的参数,以及一个t
作为目标列的参数。如果我们想要列中元素的总和B
,那么我们可以调用visit(A, 0, -1)
,即我们总是从节点A
(根的树)开始访问,它位于第 0 列,我们的目标是第 -1 列。我们得到以下信息:
因此visit(A, 0, -1) = B + I + N
正如预期的那样。
复杂度总是 O(n),其中 n 是树中的节点数,因为我们以深度优先后序访问整个树,并且我们只处理每个节点一次。
如果我们想计算每一列的总和,我们可以使用下面的算法
procedure visit(node n, int c) {
if (n.left != null)
S{c} += n.value;
visit(n.left, c - 1)
visit(n.right, c + 1)
}
并调用一次visit(A, 0)
,A
根节点在哪里。请注意,S{...}
在算法中是一个映射,其键是列号 (..., -2, -1, 0, 1, 2, ...),其值(在算法结束时)是该列中节点的值(S{1}
将是第 1 列中节点的总和)。如果我们注意索引(数组没有负索引),我们也可以使用数组而不是映射。该算法仍然是 O(n),因为我们只遍历整个树一次。但是,在这种情况下,我们需要额外的空间来存储所有列(映射或数组)的总和。如果我没记错的话,高度的二叉树h
将有2*h + 1
列。