我已经用自己的努力递归地编写了二进制搜索。在计算中间索引时,我做了int mid = (low + high) / 2
。我用Google搜索了二进制搜索的递归版本,发现计算是一个bug。因为如果(low + high)
大于整数的最大值,它可能会溢出。有两种类型的解决方案。
int mid = low + (high - low) / 2
int mid = (high + low) >>> 1
(无符号右移,它除以2)它们都像魅力一样。但是,我想知道第一个它是如何工作的。是的,它有效,但人们认为找到它的逻辑是什么。第二,它是如何工作的。 (high + low)
仍有可能溢出吗?我的意思是我的尝试因为溢出而被称为buggy,但第二个也是(high + low)
。
答案 0 :(得分:3)
第一个工作的原因是
l + (h - l)/2 = l + h/2 - l/2 = l/2 + h/2 = (l + h)/2
和有趣的是,这个“解决方案”可能仍会在大的负面low
上溢出,所以它并不能真正解决你原来的问题。
你可以在low/2 + high/2
的行上做一些事情以避免溢出,但是你需要明确地写出偶数(上面的),偶数奇数,奇偶数和奇数奇数的计算。留在int
空间。在这样做的同时,您可以使用>>>
来避免实际划分。
答案 1 :(得分:3)
在(high + low) >>> 1
中,仍然可以签名环绕,但>>>
运算符将其左操作数视为 unsigned ,因此签名的环绕无关紧要。这种方法有效,因为没有无符号环绕:high
和low
(作为有符号整数)非负,所以作为无符号整数,它们不能很大足以导致无符号环绕。
通过添加最大可能的数字可以看出添加两个有符号非负整数从未遭受无符号环绕:2 k-1 -1 + 2 k-1 -1(其中k是整数类型的位大小,通常为32),加起来为2 k -2。那还没有包装:最高可表示的数字是2 k -1。
所以这不是"溢出"在high + low
中确实导致问题,至少在一个合理的语言中,签名算术是安全的,如Java。随后将high + low
的结果作为带符号/ 2
的有符号整数处理,后面会导致问题。
答案 2 :(得分:1)
假设您的整数是16位符号(只是为了简洁;如果需要32位整数,则将它们扩展)。
范围是-32768 ... 32767。
low + (high - low) / 2
将溢出的示例:
low = -20000
high = 30000
high - low = 50000 = (after overflow) -15536
(high - low) / 2 = -7768
low - (high - low) / 2 = -27768
(high + low) >>> 1
将溢出的示例:
low = -20000
high = -10000
low + high = -30000 = (binary) 1000101011010000
(low + high) >>> 1 = (binary) 0100010101101000 = 17768
计算平均值而没有任何溢出可能性是低效的,并且可以以多种方式执行:
将您的数字除以2并使用某种"余数"
low2 = low >>> 1;
high2 = high >>> 1;
low_remainder = low & 1; // get the lowest significant bit
high_remainder = high & 1; // get the lowest significant bit
avg2 = low2 + high2; // this cannot overflow
avg = avg2 + (low_remainder + high_remainder) / 2; // this cannot overflow
我不确定这个确切的代码是否有效;这只是一个想法。
以通常方式计算平均值,然后检查"意外情况"结果(平均值小于low
或大于high
)。如果不正常,请计算出" safe"方式。
以某种方式确保您的输入不会导致溢出。这对图书馆不起作用,但通常适用于实际应用程序,在所有数字上都有10000的上限。或者也许你的号码是非负的;然后low + (high - low) / 2
实际上无法溢出。