struct node {
int value;
struct node* left;
struct node* right;
int left_sum;
int right_sum;
}
在二叉树中,从特定节点,有一个简单的递归算法来汇总其所有子值。有没有办法保存在中间步骤中计算的值,并将它们存储为子节点中的left_sum
和right_sum
?
通过在节点定义中添加struct node* parent
链接来自下而上更容易吗?
答案 0 :(得分:8)
不,这显然是一种递归练习。想想总和意味着什么。它是零加上“从根向下的所有值的总和”。
有趣的是,“从根向下的所有值的总和”是根节点的值加上“从其左侧节点向下的所有值的总和”加上“从右边节点向下的所有值的总和”。
希望你能看到我要去的地方。
递归的本质是根据具有终止条件的类似,更简单的操作来定义操作。
在这种情况下,终止条件是树的叶节点,或者为了使代码更简单,超出叶节点。
检查以下伪代码:
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)的值加上其左右子树的总和,它的左子树是空的,它的右子树是F
(1)。
因为你是递归地执行此操作,所以显式不会向上树。这是事实,递归调用返回的是具有该能力的求和值。换句话说,它发生在幕后。
你的问题的另一部分并不是真的有用,当然,可能有未说明的要求,我没有考虑到,因为它们,嗯,......未说明: - )
有没有办法保存在中间步骤中计算的值,并将它们作为left_sum和right_sum存储在子节点中?
您实际上从未重复使用给定子树的总和。在求和计算过程中,您只需计算B-and-below
子树一次,作为将其添加到A
和C-and-below
子树的一部分。
你可以存储这些值,以便B
包含值和两个总和(左和右) - 这意味着对树的每次更改都必须传播自己直到根,但它是可行的。
现在 某些可能有用的情况。例如,如果树本身很少变化,但是你非常想要总和,那么在更新时这样做是明智的,这样成本就会在很多读取中分摊。
我有时会将这种方法用于数据库(大多数情况下读取的次数比写入的频率高得多),但在“普通”二叉树中看到它是不常见的。
另一种可能的优化:只将sum作为树对象中的单独变量。将其初始化为零,然后,无论何时添加节点,都将其值添加到总和中。
删除节点时,从总和中减去其值。这为您提供了非常快速的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)
只会在第一次调用时产生费用。其他四个应该是快速的,因为总和是缓存的。