我有一棵树,想要计算每个左节点和右节点的总和。 树的布局是
3
/ \
1 8
/ \
6 15
/ \
9 20
/ \
16 25
public class BinarySearchTree {
public int left_sum = 0;
public int right_sum = 0;
public int count_sum(Node x) {
if (x == null) return 0;
left_sum += count_sum(x.left);
right_sum += count_sum(x.right);
System.out.printf("left_sum = %d right_sum=%d\n", left_sum, right_sum);
return x.data;
}
}
但是right_sum不正确。该值只是节点值。
left_sum = 0 right_sum=0
left_sum = 1 right_sum=0
left_sum = 7 right_sum=0
left_sum = 16 right_sum=0
left_sum = 32 right_sum=0
left_sum = 32 right_sum=25
left_sum = 32 right_sum=20
left_sum = 32 right_sum=15
left_sum = 32 right_sum=8
如果我在count_sum()中添加一个局部变量'int right'并稍微修改为
public int count_sum(Node x) {
int right;
if (x == null) return 0;
left_sum += count_sum(x.left);
right = count_sum(x.right);
right_sum += right;
System.out.printf("left_sum = %d right_sum=%d\n", left_sum, right_sum);
return x.data;
}
left_sum = 0 right_sum=0
left_sum = 1 right_sum=0
left_sum = 7 right_sum=0
left_sum = 16 right_sum=0
left_sum = 32 right_sum=0
left_sum = 32 right_sum=25
left_sum = 32 right_sum=45
left_sum = 32 right_sum=60
left_sum = 32 right_sum=68
现在结果是正确的。为什么?
答案 0 :(得分:2)
很难分辨出发生了什么。但是,我可以告诉你为什么这两段代码表现不同。当你说
right_sum += count_sum(x.right);
这与
相同right_sum = right_sum + count_sum(x.right);
代码以这种方式评估它:
right_sum
count_sum
(更改right_sum
)right_sum
添加到count_sum
相比之下:
right = count_sum(x.right);
right_sum += right;
与
相同right = count_sum(x.right);
right_sum = right_sum + right;
以不同的顺序做事:
count_sum
(更改right_sum
)right_sum
,的值,这是right_sum
count_sum
的新值
right
(count_sum
方法的结果)所以你可以看到为什么结果会有所不同。
我认为这里的道德只是在编写递归方法时从不使用全局变量。几乎不可能弄清楚你的代码在做什么,因为你正在使用你的全局变量并以改变全局变量的方式递归地调用该方法。 (我从经验中对此非常熟悉。我之前曾在编译器上工作,编译器本质上有很多树和递归代码。
我有大量的偏头痛试图调试代码,其中有人在递归例程中使用全局。)所以你无法分辨你正在使用什么价值以及何时使用。我做的一个例外是你可以拥有某种全局集合(例如List
或Set
)并编写添加到此集合的递归方法,但从未读过它。这很安全。
那么如果不使用全局变量,你会怎么写呢?您必须考虑如何编写一个返回所需信息的递归辅助方法。所以在这里,你可能有一个countLeftAndRight
方法返回一个带有两个字段的对象,其中一个是左边节点的总和,另一个是右边节点的总和。你可以像这样声明一个内部类:
private static class LeftAndRightSum {
public int leftSum;
public int rightSum;
public LeftAndRightSum(int l, int r) { leftSum = l; rightSum = r; }
}
因此countLeftAndRight(t)
将返回一对{L,R},其中L是左节点的总和,R是右节点的总和,并且根中的值不计入任何总和。使用递归时,有一个非常精确的定义递归方法的功能。 (实际上,它对每个程序中的每个方法都有帮助,但对递归尤其重要。)
那么countLeftAndRight
将如何运作?
countLeftAndRight
,返回{L1,R1}。countLeftAndRight
,返回{L2,R2}。countLeftAndRight
不会添加根的值,这意味着您尚未添加左节点(左子树的根)或右节点的值。现在您的结果将是{L1 + L2 + left.value
,R1 + R2 + right.value
}。您需要对空子树进行一些明显的调整。
这样做,没有全局变量,将导致代码更容易理解,并且您将更加确信它是正确的。
更多:我想到了另一个道德:如果你有一个修改全局的方法,不要在一个在其他地方使用相同全局的表达式中调用该方法。 (因此left_sum += count_sum(x.left)
应该避免,即使在非递归的情况下,因为count_sum
修改left_sum
。)在Java中,至少行为将被明确定义,但它可以使该计划难以理解。在像C ++这样的其他语言中,这可能会产生非常讨厌的结果,因为语言规则允许编译器在评估事物的顺序方面有一些自由,这意味着您可以使用不同的编译器获得不同的结果。很坏。不惜一切代价避免这种情况。