前言
- 二分查找的对象必须已经排好序,这篇文章默认用排好序的数组来掩饰。
- 本人能力有限,还望各位大佬多多包涵
- 每一个算法都会有对应的c++算法模板(是y总的模板),我只是将这个模板背后的原理解释清楚而已 。
整数二分查找
大致思想
将一个数组分为左右两个区间,判断被查找数位于左还是右区间,循环查找,直到区间的长度为一时停止。
注意:使用二分查找时一定可以返回一个下标,但是不一定有解。
如:在数组[1,2,3,5,6,7,8] 中查找元素4,虽然会返回数组下标,但是对应的元素一定不是4。所以,根据题目的要求,有时候要判断返回下标所对应的元素是否和被查找数一致。
模板
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r) //模板一
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r) //模板二
{
while (l < r)
{
int mid = l + r + 1 >> 1; //为什么+1,防止死循环
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
为了方便大家理解,这里先给出模板,再具体解释。
我们可以发现:实现这个排序算法的逻辑并不难,但很容易遇到边界问题,造成越界访问或者死循环等后果。
模板解释
1、如何确定被查找数位于左还是右区间
观察模板可以发现,我们使用if(check(mid))
进行判断。在实际的使用过程中,并不一定要写一个函数来实现这个功能。我们只要能把数组分为两个部分就可以。
- 如:假设mid是数组下标的中间位置;x是被查找的数 ;数组arr按升序(从小到大)排序
- 要将数组分为两部分,可以这样实现
(1)、if(arr[mid]>=x)
------------- 左区间为[l,mid] 右区间为[mid+1,r]
如果结果为true,那么x在左区间,下一次查找时更新查找区间r=mid
否则x在右区间 下一次查找更新查找区间l=mid+1
(2)、if(arr[mid]<=x)
---------------左区间为[l,mid-1] 右区间为[mid,r]
如果结果为true,那么x在右区间,下一次查找时l=mid
否则x在左区间,下一次查找时r=mid-1
注意:一般情况下,会将判断条件的分界点设为被查找数。 (如上例的<=x)
在上面的例子中,两种情况下的左区间和右区间划分方式分别都不相同,在下一点将会解释原因。
2、区间的划分规律
直接说结论:区间被分为[l,mid][mid+1,r] 还是 [l,mid-1][mid,r] 取决于 if()
对数组的判断结果。
(1)、前面元素判断为true,后面元素判断为false,左右区间就分为[l,mid-1][mid,r]。
- 此时就套用模板二。
为什么这么分呢?这里先不解释mid具体取值,先随便假设任意一个数都可以是mid。
如果要满足if(arr[mid]<=x)
为true
, mid可能的取值如下
我们不妨极端一点,将mid的取值分别定为true,false的分界点。
先来看if(arr[mid]<=x)
为true
的情况。
总结:当前面为true,后面为false时
判断结果为true
,被查找数的位置只能位于[mid,r]
里。
对应的,如果结果为false
,被查找数的结果只能在[l,mid-1]
里。
(2)、前面元素判断为false,后面元素判断为true,左右区间就分为[l,mid][mid+1,r]。
此时调用模板一
这个证明和上一个情况类似,我就不重复证明了。感兴趣的大伙可以自己证明。
总结:前面为false,后面为true时
判断结果为true
,被查找数的位置只能位于[l,mid]
里
对应的,如果结果为false
,被查找数的结果只能在[mid+1,r]
里。
3、解释mid的取值
可能大伙会疑惑,为什么在模板二中,mid = l+r+1>>1
呢?
其实,可以简单理解为为了防止死循环和越界
使用模板二时:如果mid = l + r>>2 (注意:向下取整)
本篇文章到这里就结束了,大伙学会了吗?