这里主要讨论的是binary search的边界情况及处理。
参考 : http://my.oschina.net/fullofbull/blog/199693?fromerr=2T6aDmtE
经典版本:(注意,边界条件迭代、循环终止条件设定,中位数计算)
int binary_search(int *A, int n, int target)
{
int low = 0, high = n - 1;
assert(A != NULL && n >= 0);
while (low <= high)
{
int mid = low + (high - low) / 2;
if(A[mid] == target)
return mid;
else if (A[mid] < target)
low = mid + 1;
else
high = mid - 1;
}
return -1;
}
这里查找的条件是:
if(A[mid] == target)
return mid;
else if (A[mid] < target)
low = mid + 1;
else
high = mid - 1;
即碰到target就返回,每次进行两次判断。
但是判断
if(A[mid] == target)
在开始时几乎没有意义,因为直接找到的可能性很小,为1/n。只有在后续的小区间判断,比较有意义。具体解释如下《代码之美》:
我们先来考虑循环的执行步骤。假设我们有一个有着 n 个元素的数组(此处n是一个很大的数值),那么从该数组中第一次找到目标的概率为 1/n(一个很小的数值),下一次(经过一次二分)的概率则是 1/(n/2)——仍然不是很大——以此类推下去。事实上,只有当元素的个数减少到了 10 到 20 的时候,一次找到目标的概率才变得有意义,而对于10 到 20 个元素进行查找需要的只是大概 4 次循环。当查找失败时(在大多数的应用中很普遍),那些额外的测试就将变成纯粹的额外开销。
我们也可以来计算一下,在什么时候找到目标值的概率能接近 50%,但请你扪心自问:在一个复杂度为 O(log2N)
的算法中,对于它的每一步都增添一个额外的复杂计算,而目的仅仅是为了减少最后的几次计算,这样做有意义吗?
所以,查找过程中可以将判断改为:
if (A[mid] < target)
low = mid + 1;
else
high = mid;
或者
if (A[mid] > target)
high = mid - 1;
else
low = mid;
这种查找还可以完成查找首个,或者最后一个出现的target。
int binary_search_first_position(int *A, int n, int target)
{
int low = -1, high = n;
assert(A != NULL && n >= 0);
while (low + 1 < high)
{
int mid = low + (high - low) / 2;
if (A[mid] < target)
low = mid;
else
high = mid;
}
if (high >= n || A[high] != target)
return -high - 1;
else
return high; // high == low + 1
}
int binary_search_last_position(int *A, int n, int target)
{
int low = -1, high = n;
assert(A != NULL && n >= 0);
while (low + 1 < high)
{
int mid = low + (high - low) / 2;
if (A[mid] > target)
high = mid;
else
low = mid;
}
if (low < 0 || A[low] != target)
return -low - 2;
else
return low; // low == high - 1
}