在C中安全二进制搜索

时间:2015-12-04 16:50:37

标签: c algorithm

理论上,大多数二进制搜索算法的实现都被打破了,因为程序可能会遇到大型数组的分段错误。例如,以下实现就是这种情况。

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);
}

我的问题是,二进制搜索算法实现的安全版本将如何?

2 个答案:

答案 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个数字的平均值,即使它们的总和超过机器精度。基本上,给定任意两个数字ab,请注意

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

没有溢出的可能性。