不,这显然是一个递归练习。想想总和是什么意思。它是零加上“从根向下的所有值的总和”。
有趣的是,“从根节点向下的所有值的总和”是根节点的值加上“从它的左节点向下的所有值的总和”加上 em>“从其右节点向下的所有值的总和”。
希望你能看到我要去的地方。
递归的本质是用类似的、更简单的、带有终止条件的操作来定义一个操作。
在这种情况下,终止条件是树的叶节点,或者为了简化代码,超出叶节点。
检查以下伪代码:
def sumAllNodes (node):
if node == NULL:
return 0
return node.value + sumAllNodes (node.left) + sumAllNodes (node.right)
fullSum = sumAllNodes (rootnode)
这就是它的全部内容。使用以下树:
__A(9)__
/ \
B(3) C(2)
/ \ \
D(21) E(7) F(1)
使用伪代码,总和是A (9) 的值加上左右子树的总和。
A 的左子树是B (3) 的值加上 其 左右子树的总和。
B 的左子树是D 的值 (21) 加上 其 左右子树的总和。
D 的左子树是NULL (0) 的值。
稍后,A 的右子树是C 的值 (2) 加上 its 左右子树的总和,它的左子树为空,其右子树为F(1).
因为您是递归地执行此操作,所以您永远不会明确沿着树的方向向上。事实上,递归调用返回的总和值赋予了这种能力。换句话说,它发生在幕后。
虽然您问题的另一部分并不是真正有用,但当然,可能存在我没有考虑到的未说明的要求,因为它们......未说明:-)
有没有办法将中间步骤中计算出来的值保存起来,作为left_sum和right_sum存储在子节点中?
您永远不会真正重复使用给定子树的总和。在求和计算期间,您将只计算B-and-below 子树一次,作为将其添加到A 和C-and-below 子树的一部分。
您可以存储这些值,以便 B 包含该值和两个总和(左和右) - 这意味着对树的每次更改都必须传播到root 也是如此,但它是可行的。
现在有一些可能有用的情况。例如,如果树本身很少更改,但您需要非常频繁地求总和,则在更新时执行此操作是明智的性能明智之举,以便在大量读取中分摊成本。
我有时会将此方法用于数据库(大多数情况下,数据库的读取频率远高于写入频率),但在“普通”二叉树中很少见。
另一个可能的优化:只需将总和作为树对象中的一个单独变量进行维护。将其初始化为零,然后,无论何时添加一个节点,都将其值添加到总和中。
当你删除一个节点时,从总和中减去它的值。这为您提供了非常快速的 O(1)“返回总和”函数,而无需在更新时向上传播。
缺点是您只有整个树的总和,但我很难想出一个需要子树总和的有效用例。如果您有这样的用例,那么我会选择类似的东西:
def updateAllNodes (node):
if node == NULL:
return 0
node.leftSum = updateAllNodes (node.left)
node.rightSum = updateAllNodes (node.right)
return node.value + node.leftSum + node.rightSum
change the tree somehow (possibly many times)
fullSum = updateAllNodes (root)
换句话说,只需在每次更改后更新整个树(或批量更改然后如果您知道发生了很多更改,则更新)。这可能比尝试将其作为树更新本身的一部分来简单一些。
您甚至可以使用单独的dirtyFlag,每当树发生变化时将其设置为 true,并在您计算和存储总和时设置为 false。然后在总和计算代码中使用它,仅在脏的情况下进行重新计算(换句话说,总和的缓存)。
这样,代码如下:
fullSum = updateAllNodes (root)
fullSum = updateAllNodes (root)
fullSum = updateAllNodes (root)
fullSum = updateAllNodes (root)
fullSum = updateAllNodes (root)
只会在第一次调用时产生费用。由于总和被缓存,其他四个应该快得令人眼花缭乱。