终极二分查找--传说十个人写九个有bug

时间:2023-12-10 15:43:44

之前写过一篇极为罗嗦的二分查找,非常得意地以为以后就可以避免踩坑了,但是今天才知道二分查找可以写的既简洁又鲁棒,唉!还是要多学习啊!

给一个按照从大到小的顺序排序好的数组a[]={1,2,3,4,7,7,7,8,9,10};

用二分查找分别求等于4,大于7的第一个数,大于等于7的第一个数,小于7的最大的数,小于等于7的最大的数。

如果写的不小心的话非常容易陷入死循环,有的时候写二分可能也会面临是l = mid还是l=mid+1这样的纠结,所以本文要证明,这几种查询的方式是可以

统一在一起的,只需要重载元素的小于号,就可以完成所有的工作

#include <bits/stdc++.h>
using namespace std;
int n;
int a[] = {,,,,,,,,,};//以升序列为例,降序也一样道理 bool cmp(int x,int v)
{
return x < v;//判断a[mid]和所要查找的边界值的关系,
//我们只需要一个小于号就可以表示出大于,等于,小于,大于等于,小于等于,这也是为什么在sort里面需要重载小于号即可。
} int b_search(int a[],int n,int v)//精确查找数组里是不是有等于v的元素
{
int l = -,r = n;//把l,r预先设定成两个边界值,在查找大于小于的时候非常有用,下面再说。
for(int mid;r-l>;){//强烈建议多用for,少用while
mid = (l+r)>>;
if(!cmp(a[mid],v)&&!cmp(v,a[mid])) return mid;//如果a【mid】不小于v,反之也不成立,只能是相等了,直接返回地址
else if(cmp(a[mid],v)) l = mid+;//a【mid】小于v此时a[mid]在v的右边,所以查找区间应该右移
else r = mid-;//反之同理
}
if(a[l] == v) return l;//如果最后得到的地址的值确实等于要查找的值,返回地址。
else return -;//否则,返回-1
} int upperequal(int a[],int n,int v)//大于等于v的最小的数,相当于stl的lower_bound函数
{
int l = -,r = n;//这里有一个隐含的意思是l是小于v的,r是大于v的那个元素,一直保持这样就可以不陷入死循环
for(int mid;r-l>;){
mid = (l+r)>>;
if(!cmp(a[mid],v)) r = mid;//注意这里判断的是a【mid】是不是大于等于v,
else l = mid;
}
return r;//因为r在变化过程中一直是大于等于v的,所以r就是答案
}
int upper(int a[],int n, int v)//二分查找大于v的第一个数,相当于stl里面的upperbound
{
int l = -,r = n;
for(int mid; r-l>;){
mid = (l+r)>>;
if(cmp(v,a[mid])) r = mid;
else l = mid;
}
return r;
}
int lower(int a[],int n, int v)//查找小于v的最大的元素
{
int l = -,r = n;
for(int mid; r-l>;){
mid = (l+r)>>;
if(cmp(a[mid],v)) l = mid;
else r = mid;
}
return l;
}
int lowerequal(int a[],int n, int v)查找小于等于v的最大的元素
{
int l = -,r = n;
for(int mid;r-l>;){
mid=(l+r)>>;
if(!cmp(v,a[mid])) l = mid;
else r = mid;
}
return l;
}
int main()
{
cout<<lowerequal(a,,)<<endl;
}

相信再也不会担心死循环了