
本文深入探讨了如何通过移除单条边将二叉树分割成两个总和相等的子树问题。文章首先分析了常见递归解法中的逻辑错误,并提供了修正后的代码。接着,提出了一种更高效的自底向上计算子树和的算法,该算法通过一次遍历收集所有子树和,从而在O(N)时间复杂度内解决问题,显著提升了性能。
二叉树等和分割问题要求我们检查一棵给定的二叉树(至少包含一个节点)是否可以通过移除一条边,将其分割成两棵总和相等的子树。如果可以,函数应返回分割后每棵子树的总和;否则,返回0。这里需要注意的是,移除一条边后,原树会分成两部分,其中一部分必然是原树的一个子树,而另一部分则是原树剩余的部分。
例如,如果一棵树的总和为 S,那么分割后两部分的和都应为 S/2。这意味着,整个树的总和 S 必须是偶数。
一种直观的思路是尝试遍历树中的每一个节点,并假设在某个节点与其父节点之间的边被移除。然后,我们需要计算被移除边所连接的两个部分的和,并判断它们是否相等。
原始代码尝试了这种递归方法,但存在几个关键问题:
不正确的断边逻辑: 原始代码中存在类似如下的条件判断:
if leftsum+tree.value == rightsum+balancesum:
return fullsum/2这个条件实际上等同于将 tree 节点及其左子树作为一个整体,与 tree 的右子树以及通过 balancesum 传入的父节点上方部分进行比较。这隐含地切断了 tree 与其父节点以及 tree 与其右子节点之间的两条边,这与问题描述中“移除一条边”的要求不符。正确的逻辑应是只移除 tree 与其左子节点或 tree 与其右子节点之间的边,或者 tree 与其父节点之间的边。
balancesum 参数传递错误: 在递归调用中,balancesum 参数用于表示当前节点上方(即父节点方向)的总和。原始代码的递归调用:
lefty = splitBinaryTree(tree.left, fullsum-rightsum) righty = splitBinaryTree(tree.right, fullsum-leftsum)
这里的 fullsum-rightsum 实际上是当前节点 tree 的值加上其左子树的总和。当递归到 tree.left 时,其父节点上方(即 tree 及其右子树和 balancesum 的总和)应作为新的 balancesum 传递。正确的 balancesum 应该包含当前节点的值、其兄弟子树的总和以及从父节点继承的 balancesum。
根据上述分析,我们可以对原始的递归方法进行修正。关键在于确保每次只考虑移除一条边,并且正确传递递归参数。
修正点:
# 辅助函数:计算以node为根的子树的总和
def icalculatesum(node):
if node is None:
return 0
return node.value + icalculatesum(node.left) + icalculatesum(node.right)
# 修正后的二叉树分割函数
def splitBinaryTree_recursive_fixed(tree, balancesum=0):
if tree is None:
return 0
# 计算当前子树的总和
fullsum = icalculatesum(tree)
# 如果当前子树的总和等于我们期望的平衡和,则找到一个分割点
# 这对应于切断当前节点与其父节点之间的边
if fullsum == balancesum and fullsum != 0: # 避免返回0作为有效分割
return fullsum
# 计算左右子树的总和(这里会重复计算,效率较低)
leftsum = icalculatesum(tree.left)
rightsum = icalculatesum(tree.right)
# 递归检查左子树
# 传递给左子树的balancesum应是:
# 原始父节点传下来的balancesum + 当前节点的值 + 右子树的总和
lefty = splitBinaryTree_recursive_fixed(tree.left, balancesum + tree.value + rightsum)
# 递归检查右子树
# 传递给右子树的balancesum应是:
# 原始父节点传下来的balancesum + 当前节点的值 + 左子树的总和
righty = splitBinaryTree_recursive_fixed(tree.right, balancesum + tree.value + leftsum)
# 如果任一递归调用找到了有效分割,则返回该分割和
if lefty != 0 or righty != 0:
# 这里返回的是整个树的总和的一半,但因为我们已经检查了 fullsum == balancesum,
# 且 fullsum 是当前子树的总和,如果找到分割,则意味着整个树的总和是 2 * fullsum
# 所以直接返回 lefty 或 righty 即可,它们已经是目标值
return lefty or righty
return 0
# BinaryTree 类定义(与原问题一致)
class BinaryTree:
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right说明: 尽管这段代码修正了逻辑错误,但由于 icalculatesum 函数在每次递归调用时都会重新计算子树的总和,导致大量的重复计算,其时间复杂度远高于 O(N)。对于大型树,这种方法效率低下。
为了提高效率,我们可以采用自底向上的方法。问题的本质是寻找一个子树,其总和恰好是整棵树总和的一半。如果我们能一次性计算出所有可能子树的总和,并存储起来,那么就可以通过一次查找来解决问题。
核心思想:
算法步骤:
代码实现:
# BinaryTree 类定义(与原问题一致)
class BinaryTree:
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
# 辅助函数:递归收集所有子树的总和
# 返回 [当前子树总和, ...所有子树总和]
def getAllTreeSums(tree, all_sums_list):
if not tree:
return 0 # 空树总和为0
left_sum = getAllTreeSums(tree.left, all_sums_list)
right_sum = getAllTreeSums(tree.right, all_sums_list)
current_subtree_sum = tree.value + left_sum + right_sum
all_sums_list.append(current_subtree_sum) # 收集当前子树的总和
return current_subtree_sum
def splitBinaryTree(tree):
if not tree:
return 0
all_sums = []
total_tree_sum = getAllTreeSums(tree, all_sums) # 此时 all_sums 包含了所有子树的总和
# 检查总和是否为偶数,且是否存在一半总和的子树
# 注意:不能是整个树的总和本身,因为这意味着没有移除任何边
if total_tree_sum % 2 == 0:
target_sum = total_tree_sum // 2
# 遍历 all_sums 列表,查找是否存在 target_sum
# 排除 total_tree_sum 本身,因为我们不能切断整个树来得到一半
for s in all_sums:
if s == target_sum and s != total_tree_sum: # 确保不是整个树的总和
return target_sum
return 0
# 优化后的 getAllTreeSums 辅助函数,直接返回列表,更简洁
def _collect_subtree_sums(node, sums_set):
if not node:
return 0
left_sum = _collect_subtree_sums(node.left, sums_set)
right_sum = _collect_subtree_sums(node.right, sums_set)
current_node_sum = node.value + left_sum + right_sum
sums_set.add(current_node_sum)
return current_node_sum
def splitBinaryTree_optimized(tree):
if not tree:
return 0
# 使用集合存储子树和,方便 O(1) 查找
subtree_sums = set()
total_sum = _collect_subtree_sums(tree, subtree_sums)
# 整个树的总和必须是偶数,并且一半的总和必须存在于某个子树中
# 且这个子树不能是整个树本身(即不能是 total_sum)
if total_sum % 2 == 0:
target_half_sum = total_sum // 2
# 如果 target_half_sum 存在于 subtree_sums 中,并且它不是整个树的总和
# (即 target_half_sum != total_sum,这在 target_half_sum == total_sum
# 只有在 total_sum 为 0 的情况下才可能发生,但题目至少一个节点)
# 更严谨的判断是 target_half_sum 必须是某个子树的和,而不能是整个树的和。
# 由于我们收集的是所有子树的和,如果 target_half_sum == total_sum
# 且 total_sum != 0,则意味着整个树的总和是 0,这是不可能的。
# 所以只需检查 target_half_sum 是否在集合中即可。
# 如果 total_sum 是 0,则 target_half_sum 也是 0,此时 0 在集合中是有效的。
if target_half_sum in subtree_sums:
return target_half_sum
return 0效率分析:
这种自底向上的方法避免了重复计算,显著提高了算法的效率。
通过采用高效的自底向上策略,我们能够以最优的时间复杂度解决二叉树等和分割问题,这在处理大规模数据时尤为重要。
以上就是二叉树等和分割:从递归修正到高效算法的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号