一、引言:
二分查找算法真是一个老生常谈的话题了,相信很多人都能解释出二分查找算法的原理并加以一定的演示。但是如果让你空手码出一段没有bug的二分查找的代码,恐怕就没有说的那么简单了(十个二分九个错)。归根结底,还是自己对算法的细节部分没有完全掌握,即区间问题。
大家一定见过不同版本的二分算法,有的while
判断条件是<=
,有的while
判断条件是<
;有的mid=r
,有的mid=r-1
。(这就是不同区间表示形式所导致的)
二、左闭右开区间:[l,r)
int binary_search1(int array[], int n, int key)
{
int mid, l = 0, r = n; //此时数组区间表示为[0,n) 等价于 [0,n - 1]。
while (l < r){ /*合法的区间是二分查找的前提,极限情况下左右边界最后汇聚于一点,
所以在左闭右开的情况下,合法区间的左边界<右边界。不存在[a,a)这种区间*/
mid = l + (r - l) / 2;
if (array[mid] > key) { //查找的值小于arrary[mid],即在中点左侧。[l,r) = [l,mid - 1] + [mid,r)。
r = mid; //区间更新为[l,mid) 等价于 [l,mid - 1]。
}
else if (array[mid] < key) { //查找的值大于arrary[mid],即在中点右侧。[l,r) = [l,mid] + [mid + 1,r)。
l = mid + 1; //区间更新为[mid + 1,r)
}
else {
return mid; //array[mid] == key,即找到key值,返回对应的数组下标
}
}
return -1; //没有找到key值,返回-1
}
三、左闭右闭区间:[l,r]
int binary_search2(int array[], int n, int key)
{
int mid, l = 0, r = n - 1;//此时数组区间表示为[0,n - 1] 等价于 [0,n)。
while (l <= r) { /*合法的区间是二分查找的前提,极限情况下左右边界最后汇聚于一点,
所以在左闭右闭的情况下,合法区间的左边界<=右边界。存在[a,a]这种区间*/
mid = l + (r - l) / 2;
if (array[mid] > key) { //查找的值小于arrary[mid],即在中点左侧。[l,r] = [l,mid - 1] + [mid,r]。
r = mid - 1; //区间更新为[l,mid - 1] 等价于 [l,mid)。
}
else if (array[mid] < key) { //查找的值大于arrary[mid],即在中点右侧。[l,r] = [l,mid] + [mid + 1,r]。
l = mid + 1; //区间更新为[mid + 1,r]
}
else {
return mid; //array[mid] == key,即找到key值,返回对应的数组下标
}
}
return -1; //没有找到key值,返回-1
}
四、总结:
1.[a,b)
2.[a,b]
当我们选择了一种区间的表达形式后,就要在代码体现左右边界的地方进行相应的处理。不要混用区间的表达形式,比如起初你选择[a,b)这种区间表达形式,而接下来又不知不觉把区间表达形式写成了[a,b]。这种错误完全是可以避免的,不要把问题复杂化,简单的说就是一条路走到底。
相信很多二分查找的bug都源于区间开闭问题,只要理解了两种区间的相同之处和不同之处,无论是什么版本的二分你都可以一眼看出其中的对与错。