二分搜索
(ps:参考书籍:挑战程序设计)
二分法查找:通过不断缩小解存在的范围,在有序数组中查找特定元素的搜索算法。
经常可见 二分法 与其他算法结合的题目
二分法的思路:
(1)首先,从数组的中间开始搜索,如果该位置的值刚好是目标,则表示找到,结束搜索。
(2)如果第一步的搜索到的值 大于目标,则把数组分成两半,在数组右边区域查找,然后重复步骤(1)的操作。
如果第一步的搜索到的值 小于目标,则把数组分成两半,在数组左边区域查找,然后重复步骤(1)的操作。
(3)如果到数组分成仅剩一个元素还是没找到,就表示查找不到。
朴素的从左到右查找,时间是O(n),二分法查找的时间 O(logn)。
通常写法大致为:
//二分
int l=0,r=n; // l:左边 r:右边
while(l<=r){
int mid = (l+r)/2;
if(a[mid]== k){
cout<<mid;
break;
}
if(a[mid] > k){
r = mid-1;
}else{
l = mid+1;
}
}
相关运用
多好时候,我都是写成大概上面那样, L ,R 是数组的起始下标和尾下标。
但是,有些题,由于题目的特性,上面写法做不出来(也可能是我太弱55)
比如下面这道:
通常的那种写法每次是下标向前移或向后移,是用在求一个数是否在数组里面出现
而这种题是要求实数的,而且数值并没有在数组里面出现。
那怎么办? 应该把L ,R当成是答案的范围,而后用二分求。
写出来大概是这样的:
#include<iostream>
#include<cstdio>
using namespace std;
#include<cmath>
const int maxn = 10005;
int n,k;
double a[maxn];
bool f(double x){
int cnt=0;
for(int i=1;i<=n;i++){
cnt += (int)(a[i]/x);
}
return cnt>=k;
}
int main(){
cin>>n>>k;
double len = 0;
for(int i=1;i<=n;i++){
cin>>a[i];
len += a[i];
}
// 二分
double l =0,r = len;
for(int i=0;i<100;i++){
double mid = (l+r)/2;
if(f(mid)){
l = mid;
}else{
r = mid;
}
}
// cout<<l-1;
printf("%.2f",l);
return 0;
}
这种二分,L,R不再是数值的左右标,而是目标答案的左右范围 ,
写法模板:
// 二分
int l =-1,r = n;
while(r-l>1)
int mid = (l+r)/2;
if(a(mid) >=k){ //如果解满足条件,则解存在的范围变为(l,mid]
r = mid;
}else{
l = mid; //如果解不满足条件,则解存在的范围变为(mid,r]
}
}
cout<<r;
最大化最小值 系列问题
类似最大化最小值,最小化最大值,最大化平均值等,通常都用到二分搜索,
比如下面这种, 其他不用怎么说了吧,大致差不多。
#include<iostream>
#include<cstdio>
using namespace std;
#include<algorithm>
const int maxn = 100005;
int n,m;
int x[maxn];
bool f(int d){
int last = 0;
for(int i=1;i<m;i++){
int k=last+1;
while(x[k] - x[last] <d && k<n){
k++;
}
if(k ==n){
return false;
}
last = k;
}
return true;
}
int main(){
cin>>n>>m;
for(int i=0;i<n;i++){
cin>>x[i];
}
sort(x,x+n);
int l=0,r=1e8;
while(r-l>1){
int mid = (l+r)/2;
if(f(mid)){
l = mid;
}else{
r = mid;
}
}
cout<<l;
return 0;
}