目录
0 、二分法简介
- 简介
二分查找(英语:binary search),也称折半搜索(英语:half-interval search)、对数搜索(英语:logarithmic search),是用来在一个有序数组中查找某一元素的算法。
- 工作原理
以在一个升序数组中查找一个数为例。
它每次考察数组当前部分的中间元素,如果中间元素刚好是要找的,就结束搜索过程;如果中间元素小于所查找的值,那么左侧的只会更小,不会有所查找的元素,只需到右侧查找;如果中间元素大于所查找的值同理,只需到左侧查找。
- 性质
时间复杂度
二分查找的最优时间复杂度为O(1) 。
二分查找的平均时间复杂度和最坏时间复杂度均为 O(logn)。因为在二分搜索过程中,算法每次都把查询的区间减半,所以对于一个长度为n 的数组,至多会进行 O(logn) 次查找。
空间复杂度
迭代版本的二分查找的空间复杂度为O(1) 。
递归(无尾调用消除)版本的二分查找的空间复杂度为O(logn) 。
- 应用
- 二分查找
- 二分答案
- 二分法通常可以和其他算法一起考察,通常都成为其它算法的载体。
- 难度范围非常广,下至 NOIp 第一题,上至 NOI/ICPC World Final 压轴题
- 二分法应用十分广泛,很多其他算法都有间接用到二分算法的情况。
- 二分查找思路
二分查找是一种在有序数组中查找某一特定元素的查找算法。
查找过程从数组的中间元素开始:如果中间元素正好是要查找的元素,则查找过程结束;
如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。
如果在某一步骤数组为空,则代表找不到。
「连续」「单调」「确切答案」 解通常是唯一的,或者不存在。
数组a二分查找某元素的模板:
static int find(int x) {
int l=1,r=n;
while(l<r) {
int mid=(r+l)/2;
if(x<=a[mid])r=mid;// 如果中间值大于等于目标值就继续往左边找
else l=mid+1;
}
if(a[l]==x)return l;
else return -1;
}
- 二分答案
二分答案,即通过题目中包含的单调性对答案进行二分。大多数二分答案可以看做是标准二分法套了一个奇奇怪怪的函数。 解答需要使用二分答案的题目,最重要的是观察出单调性。
通常来说,二分答案题目为下列两种中的一种:给定一个评价函数,求评价函数的最小值 / 最大值 。给定一个条件,要求在满足条件的同时,使得代价最小。计算函数或者判断符合条件需要消耗极长的时间;或者评价函数的值域太大了,不能一 一计算。
1、二分入门-洛谷P1024一元三次方程求解
oj:https://www.luogu.com.cn/problem/P1024 标签:数论,数学,二分答案,枚举,暴力,分治,NOIp提高组2001(或之前)
思路:
package 二分查找与二分答案;
import java.util.Scanner;
public class P1024一元三次方程求解 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
double a=in.nextDouble();
double b=in.nextDouble();
double c=in.nextDouble();
double d=in.nextDouble();
double y1,y2,j;
for(double i=-100;i<=100;i+=0.001) {
j=i+0.001; //j取到保留的小数位下一位
y1=a*i*i*i+b*i*i+c*i+d;
y2=a*j*j*j+b*j*j+c*j+d;
if(y1*y2<=0)//y1,y2异号或其一为0
System.out.printf("%.2f ",(i+j)/2);
}
}
}
2、Java快速输入+二分查找 P2249查找
oj:https://www.luogu.com.cn/problem/P2249 标签:二分答案
package 二分查找与二分答案;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class P2249查找 {
static int[] a;
static int n;
public static void main(String[] args) throws IOException {
StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
PrintWriter out=new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
n=(int)in.nval;//相当于int n = in.nextInt();
in.nextToken();
int m=(int)in.nval;
a=new int [n+1];
int[] b=new int [m+1];
a[0]=-1;//有可能会要查找等于0的数,所以我们把第一个改成-1
for(int i=1;i<=n;i++) {
in.nextToken();
a[i]=(int)in.nval;
}
for(int i=1;i<=m;i++) {
in.nextToken();
b[i]=(int)in.nval;
out.print(find(b[i])+" ");
}
out.flush();
}
static int find(int x) {//参数的数目直接影响调用函数的速度,参数越多,调用函数就越慢.这题如果函数是两个参数,就会超时。
int l=1,r=n;
while(l<r) {
int mid=l+(r-l)/2;
if(a[mid]>=x)r=mid;// 如果中间值大于等于目标值就继续往左边找
else l=mid+1;
}
if(a[l]==x)return l;
else return -1;
}
}
java快速输入输出用法见:https://blog.csdn.net/qq_44491991/article/details/115186693
3、二分查找- 洛谷P1678 烦恼的高考志愿
OJ:https://www.luogu.com.cn/problem/P1678 标签:模拟,贪心,排序,二分查找,高性能
package 二分查找与二分答案;
import java.util.Arrays;
import java.util.Scanner;
public class P1678烦恼的高考志愿 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int m=in.nextInt();
int n=in.nextInt();
int[] a=new int[m];
int[] b=new int[n];
for(int i=0;i<m;i++)a[i]=in.nextInt();
for(int i=0;i<n;i++)b[i]=in.nextInt();
Arrays.sort(a);//把每个学校的分数线从小到大排序
int l,r,mid;
long ans=0;
for(int i=0;i<n;i++) {//为每个学生通过二分法查找学校
l=0;r=m-1;
while(l<r-1) {
mid=(l+r)/2;
if(b[i]>=a[mid])l=mid;
else r=mid;
}
ans+=Math.min(Math.abs(a[l]-b[i]),Math.abs(a[r]-b[i]));//加上两个绝对值中最小
}
System.out.println(ans);
}
}
4、二分答案+java快速输入 洛谷P1873 砍树
oj:https://www.luogu.com.cn/problem/P1873 标签:二分答案,高性能
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
public class Main {
public static void main(String[] args) throws IOException {
StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
int n=(int)in.nval;
in.nextToken();
int m=(int)in.nval;
int[] a=new int[n];
int l=0,r=0;
for(int i=0;i<n;i++) {
in.nextToken();
a[i] = (int)in.nval;
r=Math.max(r,a[i]);//记录最高的树
}
//二分模板
while (l<=r) {
int mid=(l+r)/2;
if(check(mid,a,m))//如果mid是可行解
l=mid+1;//那么试着慢慢提高高度,最低就砍到mid+1
else //mid不可行
r=mid-1; //那么必须得砍得比mid还要低,所以最高的高度为mid-1
}
out.print(r);//例举法,ans也可是l-1
out.close();
}
public static boolean check(int x,int[] a,int m) {
// int sum=0; //气死了:这样分数是70。
// for(int i=0;i<a.length;i++) {
// if(a[i]>x)sum+=a[i]-x;
// }
// return sum>=m;
int sum = 0;
for(int i = 0;i < a.length;i++) {
if(a[i] > x) {
sum += (a[i] - x);
if(sum >= m)return true;//只要满足需求即可返回了!防止sum超范围。
}
}
return false;
}
}
未完待续。。。