二分查找应该算是比较基本的算法了,但考虑到各种边界问题,真正能把二分查找写对的人不多。
一些模板
在阅读此博客之前,如果您还没有接触过二分查找,可以看看这个
二分的模板有好几套,我在这里分享一下,并说明用法。
整数二分:
第一套模板是我见的最多的,适用场景:我们要查找的内容并不满足一个区间,而是一个点,就比如说在有序数组中查找值时,就适合用此模板:
while(l <= r) {
mid = l + r >> 1;
if(check(mid) > key) {
r = mid - 1;
} else if(check(mid) < key) {
l = mid + 1;
} else {
return mid; //找到了就返回mid
break;
}
}
return -1; //否则返回-1
当然,第一个模板是完全可以被后两个取代的
第二套模板适合答案在一个区间内,而我们要求出满足题意的第一个元素,也就是大于等于key的第一个元素
区间被我们划分为:\([l,mid]\)和\([mid + 1, r]\)
while(l < r) {
int mid = (l + r) / 2;
if(A[mid] > key) //如果要求大于等于,可以加上等于
r = mid; //因为要找的是大于key的第一个元素,A[mid]已经大于key了,所以mid + 1后面的排除
else
l = mid + 1; //如果A[mid]小于key,那么A[mid]以及比A[mid]小的数都需要排除
}
第三套模板也适合答案在一个区间内,而我们要求出满足题意的最后一个元素,也就是小于等于key的最后一个元素
区间被我们划分为\([l, mid - 1]\)和\([mid, r]\)
while(l < r) {
int mid = (l + r + 1) / 2; //这里要加上1,考虑如下一种情况,最后只剩下A[i],A[i + 1],如果不加1,那么mid = i,如果A[mid] < key,执行更新操作后,left = mid,right = mid + 1,就会是死循环
if(A[mid] < key)
l = mid; //如果A[mid]小于key,说明比A[mid]更小的数肯定不是小于key的最大的元素了,所以要排除mid之前的所有元素
else //反之,如果大了,那就说明比A[mid]更大的数肯定不可能为答案,所以排除mid及其之后的所有元素
r = mid - 1;
}
最后两种情况的循环跳出条件是left<right,为什么不是小于等于呢?因为我们的区间变换思路是不断的舍去不可能是解的区间,最后只剩下一个数就是我们的解。而第一种情况就算最后只剩一个数也有可能不是解,所以需要使用小于等于
实数二分
实数二分就很好写了,不用考虑边界问题
while(r - l < 1e-8) { //因为精度问题,所以这里不能用l < r了,而是需要迭代很多次直到l与r差别不大
double mid = (l + r) / 2;
if(check(mid) < key ) //随便写
mid = r;
else
mid = l;
}






Comments NOTHING