理论上,大多数二进制搜索算法的实现都被打破了,因为程序可能会遇到大型数组的分段错误。例如,以下实现就是这种情况。
int binarysearch(int x, int *v, int n) {
int l, h, m;
l = 0;
h = n - 1;
while (l <= h) {
m = (l + h) / 2;
if (x < v[m]) h = m - 1;
else if (x > v[m]) l = m + 1;
else return m;
}
return -1;
}
int main (void)
{
int n = (INT_MAX/4) * 3;
int *v = calloc(n, sizeof(int));
(void) binarysearch(1, v, n);
cfree(v);
}
我的问题是,二进制搜索算法实现的安全版本将如何?
答案 0 :(得分:5)
代码中有问题的部分是它的中点计算:
m = (l + h) / 2;
会在int
溢出时产生错误的结果。您可以通过切换到long long
计算或使用安全公式来避免这种情况:
m = (h - l)/2 + l;
有关详细信息,请参阅Binary Search - Arithmetic。
答案 1 :(得分:3)
指出错误发生的位置会很有帮助 - 即如果m = (l + h) / 2;
溢出正整数范围,l + h
的计算可能会失败。在这种情况下,答案将变为负数,并且有符号整数除法将传播符号位,产生较小的负数,当它用作数组索引时,将被解释为非常大的无符号正数。
我不记得我在哪里看过它,但是有一个可爱的技巧可以让你安全地计算2个数字的平均值,即使它们的总和超过机器精度。基本上,给定任意两个数字a
和b
,请注意
a = (a & b) | (a & ~b) # Each bit in a is either shared with b, or not
= (a & b) + (a & ~b) # Since these two terms share no bits
b = (a & b) | (b & ~a)
= (a & b) + (b & ~a) # Likewise
所以
(a + b) / 2 = [ (a & b) + (a & ~b) + (a & b) + (b & ~a) ] / 2
= [2*(a & b) + (a & ~b) + (b & ~a)] / 2
= [2*(a & b)] / 2 + [(a & ~b) + (b & ~a)] / 2
= (a & b) + [(a & ~b) + (b & ~a)] / 2
最后,请注意RHS上的表达式(a & ~b) + (b & ~a)
只是a或b中的所有位,但不是两者中的所有位 - IOW,它是a ^ b
。因此我们有
(a + b) / 2 = (a & b) + (a ^ b) / 2
没有溢出的可能性。