目录
二分查找
二分查找分为靠左和靠右。
是一种节省时间的算法。
由于每次运算减少一半的计算量,所以算法时间为O(logn)。
下图为每一个去枚举和二分查找的次数对比
当问题是求某一个解的时候如果我们去线性枚举,那数据点一大我们就会超时,因此我们使用二分的思想,从答案的可能返回[l,r]中找到中间值mid,如果答案mid太小不符合条件,我们则继续看右区间,这样可以做到O(logn)的复杂度,非常高效
前提:答案区间具有单调性(线性递增,l越大,线段cnt越大/小)
二分查找的难点则在check函数,检查当前答案是否符合条件
整数二分?
靠左查找
特点:求......最大值的最小(符合条件的最大值)
进行查找时,尽可能的找到符合条件的靠左值。此时,需要注意如果当前切割点mid符合条件,那应该保留答案,且删除右区间,靠左继续二分,直到不可二分。
while(l<r){
int mid=l+r>>1;//mid靠左分割,防止死循环
if(a[mid]>=x) r=mid;//符合条件,保留答案在左区间
else l=mid+1;//不符合条件,删除左区间
}
靠右查找
特点:求......最小值的最大(符合条件的)
进行查询时,尽可能的找到符合条件的靠右值/最大值。此时需注意如果当前切割点mid符合条件,那应保留答案,且删除左区间,靠右继续二分,直到不可再分。
while(l<r){
int mid=l+r+1>>1;//mid靠右分割,防止死循环
if(x>=a[mid]) l=mid;//符合条件,保留答案删除左区间
else r=mid-1;//不符合条件,删除右区间
}
总体代码展示
#include<bits/stdc++.h>
using namespace std;
int n,m,a[1145141];
int ask(int x){
int l=1,r=n;
while(l<r){
int mid=l+r+1>>1;
if(x>=a[mid]) l=mid;
else r=mid-1;
}
if(a[l]!=x) return -1;
return l;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
int x;cin>>x;
cout<<ask(x);
return 0;
}
实数二分
对于实数的二分,与整数二分主要的差异体现在以下两个方面:
1.实数不会出现因整数取整而导致的mid偏向问题。即double mid=(l+r)/2,不用考虑会因为mid错误导致死循环。
2.实数中,两个相邻的数字间隔不一定是1,所以不能用加减1来进行寻找。最后找到的数字也可能是相似值,所以只要满足精度即可。
一般来说如果题目要求保留k位小数,此时l,r是不一样的。我们会把精度设置到小数点后k+2位。(k+1位会对第k位的进位产生很大影响,k+2位相对好一点。),此时输出l或r基本相同
#include<bits/stdc++.h>
using namespace std;
double n,eps=1e-8;
bool check(double x){
return x*x*x>=n;
}//检验答案是否可行
int main(){
cin>>n;
double l=1,r=n;
while(r-l>eps){
double mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid;
}
printf("%6lf",l);
return 0;
}
//求n的三次方根
二分查找相关函数
若存在序列a={0,1,3,3,3,5,8}
1.binary_search(a+1,a+n+1,x)
查找单调序列中,在指定区域内[1,n]是否存在目标值x。存在返回true,不存在返回false。
int k=binary_search(a+1,a+7,3);//k=1
2.lower_bound(a+1,a+n+1,x)
查找不降序列中,在指定区域内[1,n]大于等于目标值x的第一个元素所在地址。(靠左查找)
int pos=lower_bound(a+1,a+7,3)-a;//元素位置在a+2,因此pos=2。
3.upper_bound(a+1,a+n+1,x)
查找不降序列中,在指定区域内[1,n]大于目标值x的第一个元素所在地址。(靠右查找)
int pos=lower_bound(a+1,a+7,3)-a;//元素位置在a+5,因此pos=5。
谢谢各位