目录
前言引入
(听说,只有10%的程序员能一次性写对二分喔)
当有一大堆的数据,需要我们在这么一大堆的数据中,找到我们想要的那个数据。毫无疑问,打个循环,慢慢开始遍历,确实可以找到,但是这样耗费的时间就太长了。
因此就诞生了二分查找。甚至还衍生出了二分答案。
同时,根据二分的原理,还可以衍生到三分甚至n分。
以及实数域上的二分(例如一元三次函数的求解)
二分查找
整数域的二分
二分的原理:
基于高中数学的零点问题,在一个有序区间【left,right】内,要查找某个数,就看要查找的这个数,和这个区间最中间的数进行比较,如果要查找的数大于中间数,那么接下来,就在区间【mid+1,right】中进行查找;反之,就查找区间【left,mid】。依此类推。
例如10个数据 1,2,3,4,5,6,7,8,9,10。
要查找的key:7
首先找到最中间的数:(left+right)/2 可得知为5。
然后,将5与key(7)比较,发现7>5。因此,下一步就在区间【6,10】中进行查找。
找到最中间的数:8。
然后,将8与key(7)比较,发现7<8。因此,下一步就在区间【6,8】中进行查找。
找到最中间的数:7。
将7与key(7)比较,欸,刚好等于key,所以接下来就退出查找,因为已经找到啦!
递归式
int BinarySearch(int left,int right,int key){
if(left==right) {
if(a[left]==key) return left;
else return -1;
}
int mid=(left+right)>>1;//位运算和除2一样
if(a[mid]>=key) return BinarySearch(left,mid,key);
else return BinarySearch(mid+1,right,key);
}
非递归式
int BinarySearch(int left,int right,int key){
while(left<right) {
int mid=(left+right)>>1;//位运算和除2一样
if(a[mid]>=key) right=mid;
else left=mid+1;
}
if(a[left]==key) return left;
else return -1;
}
从测评可以看出,非递归式的空间复杂度要比递归式小很多
当然,以上代码只局限于在单调递增序列中查找>=key的第一个数据
例如1 2 2 2 3 4 5;key==2,那么找到的位置即是
^
要想找到最后一个2,则模板改为:
int BinarySearch(int left,int right,int key) {
while(left<right) {
int mid=(left+right+1)>>1;
if(a[mid]<=key) left=mid;
else right=mid-1;
}
if(a[left]==key) return left;
else return -1;
}
分区段二分
假设数据量过大,要我们查找【1,1e18】之间的某个数,而恰好,要找的数又是极端情况。
难免时间也会过长。这个时候可以分区段二分。
例如,可以分成100段,第一段就是【1,1e16】,第二段就是【1e16,2e16】.....
然后我们查找1e16是否满足, 若满足,则在【1,1e16】进行二分或者又一次分段查找。
若不满足,则查找2e16是否满足。
分区段二分的上限很高,分的度不好把控,也就是说根据分的区段的不同,算法时间也会有差异。
分的区段过多,相当于遍历了,并非二分,分的过少,极端情况二分也不合适。
所以最优解很难求出,需得大量的实践。然而,换个数据,最优解又不同了。
实数域的二分
实数域二分典型的例子就是一元三次函数求解。当然,也运用了一下分区段二分的思想。
已知精度
通常,实数域的二分,在已知精度的情况下,为了避免误差,会继续保持精度在后两位进行查找。
例如 :已知精度为0.001,实际上查找是在0.00001的精度,也就是1e-5
double BinarySearch(double left,double right){
while(left+eps<right) { //eps为精度
double mid=(left+right)/2;
if(check(mid)) right=mid;//check为与查找条件
else left=mid;
}
if(check(left)) return left;//check为结束条件
else return -1;
}
未知精度
精度不容易确定,就采用循环固定次数的方式。固定查找n次,总有一次能找到我们满意的值。
double BinarySearch(double left,double right){
for(int i=1;i<=n;i++) {
double mid=(left+right)/2;
if(check(mid)) right=mid;
else left=mid;
}
if(check(left)) return left;//check为结束条件
else return -1;
}
以下为洛谷一元三次函数求解的题
https://www.luogu.com.cn/problem/P1024
以下就是我ac的代码(不是最优,可供参考)
#include<iostream>
#include<algorithm>
#include <cstdio>
using namespace std;
double a,b,c,d;
double f(double x) {
return a*x*x*x+b*x*x+c*x+d;
}
double BinarySearch(double left,double right) {
//若是单调递减
if(f(left)>0) {
while(left+1e-3<right) {
double mid=(left+right)/2;
if(f(mid)<-0.0001) right=mid;
else left=mid;
}
}
//若单调递增
else {
while(left+1e-3<right) {
double mid=(left+right)/2;
if(f(mid)>0.0001) right=mid;
else left=mid;
}
}
return left;
}
int main(){
cin>>a>>b>>c>>d;
for(double i=-100;i<=100;i++) {
if(f(i)>=-0.0001&&f(i)<=0.0001) printf("%.2lf ",i);
else if(f(i)*f(i+1)<0) {
printf("%.2lf ",BinarySearch(i,i+1));
i++;//这里i++防止重复
}
}
cout<<endl;
return 0;
}
二分答案
二分答案问题,其原理可对应高中学的函数,在定义域x∈【a,b】内,要求是单调的,并且每一个x都有其对应的y∈值域【c,d】。x增加,那么y的值也会对应增加或减少。
二分答案问题即:在题目限制的y的条件下(y大于某个数或小于某个数),找到最优的x。
但是,从最左边开始遍历查找,时间难免过长。因而采用二分查找的方式,这就是二分答案算法。
下面以一次函数 y=kx+b为例。
题目已知要求:y<=c。要我们在定义域【m,n】内找到一个数,让其对应的y达到要求 。
由图可知,x∈【m,a】都是满足的,因此最优解就是a,所以,我们在区间【m,n】中采用二分查找的方法,就能找到这个a。
以下就是解决这个问题的模板代码
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
int k,b,y;//k,b为题目已知条件
//啥?你说题目没明给?那自己推条件呀
//y为题目要求输入的值
int f(int x) {
return k*x+b;
}
bool check(int key) {
if(f(key)>=y) return true;
else return false;
}
int main() {
int left,right;//left,right为题目已知条件
//啥?你说题目没明给?那自己推条件呀
//这里的left,right就是一次函数的定义域m,n。
cin>>y;
while(left<right) {
int mid=(left+right)>>1;
if(check(mid)) right=mid;
else left=mid+1;
}//check(mid)即为判断条件
cout<<left<<endl;
system("pause");
return 0;
}