我有以下代码,当完整的二进制树为layer
层高时,它返回树中的节点数:
public static long nNodesUpToLayer(int layer) {
if (layer < 0) throw new IllegalArgumentException(
"The layer number must be positive: " + layer );
//At layer 0, there must be 1 node; the root.
if (layer == 0) return 1;
//Else, there will be 1 + 2 * (the number of nodes in the previous layer) nodes.
return 1 + (2 * nNodesUpToLayer(layer - 1));
奇怪的是,当我将63
输入函数(产生此函数的最小值)时,它会返回-1
。在62
,它会返回9223372036854775807
,因此这似乎是由溢出引起的。
它不应该给我回Java长期的最小值+它溢出的数量吗?无论我给出的输入是什么(通过62
),它总是会返回-1
,而不是一个看似随机的数字,我期望从溢出中获得。
我不完全确定如何调试它,因为它是递归的,并且只有在函数达到基本情况时才会评估我感兴趣的值。
答案 0 :(得分:57)
你是对的,它是64位有符号整数的溢出错误。它转到-1
而不是最小整数值的原因是因为你加倍,而不是简单地添加一个。
9223372036854775807
为63 1
s:
0111 1111 ... 1111 1111
要以二进制加倍,只需执行左移:
1111 1111 ... 1111 1110
但是,在Two's Complement中的这个数字不是9223372036854775807
的两倍,而是-2
。然后,当然,您需要先添加1
,然后才能返回-1
结果。
答案 1 :(得分:15)
实际上,它会返回正确的金额。只是“它溢出的数量”完全正确的答案-1
:)
考虑一下:
对于2^n - 1
层,完整二叉树中的节点数为n
。因此,它的二进制表示为0000...00111...111
,其中1
s的数量恰好是层数减1.只要达到long
的长度,就会被截断11...11
,正是-1
答案 2 :(得分:10)
我总是喜欢用这样的东西进行可视化。
(min long)
v
<--------------------||--------------------------------------->
^ ^
(max long, n) -1
n
的位置是9223372036854775807 - 在乘以2之前的值。而不是乘法,而是将其视为加法。 n + n
。通过在数字线上看到它,你可以看到你最终在-2。你基本上已经超过了大多数负数。
因此,与其他人相比,我的答案提供了一些有意义的东西,在这种情况下,一个有用的工具是将算术分成多行以便进行调试。你可以写:
int a = nNodesUpToLayer(layer - 1);
int b = 2 * a;
int c = 1 + b;
return c;
你基本上按照你的预期强制执行操作顺序(这可能会帮助你意识到程序按照你想要的顺序执行操作),但是它也允许你进入调试器并且查看计算的中间值。在这里您会注意到b == -2
。为什么b == -2
?好吧,一定是因为2 * a == -2
等等。