二分法的概念
在数学中,[a,b]上连续不断且f(a)·f(b)<0的函数y=f(x),通过不断地把函数f(x)的零点所在的区间一分为二,使区间的两个端点逐步逼近零点,进而得到零点近似值的方法。
二分法的引入
二分法作为一个常用的算法,我们可以先通过一个简单的题目进行引入:
题目是这样的,一组数按照升序进行排序,要求是,在这一组数据中,我么要找到目标数字target,正常的情况下,我们肯定会利用循环的方法,将数据进行遍历,从中找到满足条件的数据,从而输出,但是这样做的话,如果是数据较少的情况下还可以进行处理,但是当数据非常打的时候,在利用遍历的方法就会出现超时的情况。那么我们可以利用二分法对这道题进行求解,我们可以先定义一个变量l,代表最左边的数字的下表,定义一个r代表最右边的元素的下标,再定义一个中间变量m,m=(l+r),此时m就代表中间一个元素的下标,我们将目标数字与中间的那个元素进行对比,如果相等就输出m,如果中间元素比目标书数字大,我们就可以把此时的右边界r变成m左边的一个元素;同理,如果中间的那个元素的值比目标元素小,l就变成m右边的一个元素。如图:
直到l和m和r都相等,此时就可以找到了目标数
代码部分如下:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int n=in.nextInt();
int a[]=new int[n];
for(int i=0;i<a.length;i++) {
a[i]=in.nextInt();
}
int x=in.nextInt();
int l=0,r=a.length-1,m=(r+l)/2;
while(l<=r)
{
m=(r+l)/2;
if(a[m]==x)
break;
else if(a[m]>x)
r=m-1;
else
l=m+1;
}
if(a[m]==x)
System.out.println(m);
else
System.out.println("NOT FOUND!");
}
}
例题一:二分查找
输入n值(1<=n<=1000)、n个非降序排列的整数以及要查找的数x,使用二分查找算法查找x,输出x所在的下标(0~n-1)及比较次数。若x不存在,输出-1和比较次数。
思路分析:从题目中的意思我们可以发现:这是一组升序排列的数据,每检测一次,记录一次,如果最终能够找到目标数据,则输出该数据的下标和次数,与上面的引入类似,不同的是如果查找不到需要输出-1和查找的次数。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int sum=0;//定义一个sum用来计算查找的次数
int n=in.nextInt();
int a[]=new int[n];
for(int i=0;i<n;i++)
a[i]=in.nextInt();
int x=in.nextInt();
int l=0,m,r=a.length-1;
m=(l+r)/2;
while(r>=l){//因为r是右边界,l是左边界,所以要想有中值,则必须要保证r>l,也就是循环的条件
sum++;//每进行一次循环(查找)都自增一次
m=(l+r)/2;
if(a[m]==x)
break;//循环结束的另一个条件,此时的目标值被找到
else if(a[m]>x)
r=m-1;//否则就通过比较中值与目标值的大小,判断此时的中值应该作为“右边界”还是“左边界”
else
l=m+1;
}
if(a[m]==x) {//目标值被找到
System.out.println(m);
System.out.println(sum);
}
else if(a[m]!=x) {
System.out.println(-1);
System.out.println(sum);
}
}
}
我们发现,循环结束的条件有两个,一个是目标值被找到,这样就可以直接确定它的下标,第二种是经过二分查找之后没有找到目标值,就需要左边界小于等于右边界来结束。
例题二:二分法求多项式单根
二分法求函数根的原理为:如果连续函数f(x)在区间[a,b]的两个端点取值异号,即f(a)f(b)<0,则它在这个区间内至少存在1个根r,即f®=0。
二分法的步骤为:
检查区间长度,如果小于给定阈值,则停止,输出区间中点(a+b)/2;否则
如果f(a)f(b)<0,则计算中点的值f((a+b)/2);
如果f((a+b)/2)正好为0,则(a+b)/2就是要求的根;否则
如果f((a+b)/2)与f(a)同号,则说明根在区间[(a+b)/2,b],令a=(a+b)/2,重复循环;
如果f((a+b)/2)与f(b)同号,则说明根在区间[a,(a+b)/2],令b=(a+b)/2,重复循环。
本题目要求编写程序,计算给定3阶多项式
在给定区间[a,b]内的根。
上代码
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
double a3,a2,a1,a0;
a3=in.nextDouble();
a2=in.nextDouble();
a1=in.nextDouble();
a0=in.nextDouble();
double a=in.nextDouble();
double b=in.nextDouble();
double m=(a+b)/2;
while((b-a)>0.001) {//循环结束的条件
m=(a+b)/2;
if(f(a3,a2,a1,a0,m)==0)//循环结束的条件(找到函数的零点)
break;
else if(f(a3,a2,a1,a0,m)*f(a3,a2,a1,a0,a)>0)//完全根据题意,如果中值点的函数的符号与a点的函数的符号相同
a=m;
else//与b的函数值符号相同
b=m;
}
System.out.printf("%.2f",m);
}
private static double f(double a3,double a2,double a1,double a0,double n)//多项式函数
{
return a3*n*n*n+a2*n*n+a1*n+a0;
}
}
例题三:剪绳子
有 N 根绳子,第 i 根绳子长度为 Li,现在需要 M 根等长的绳子,你可以对 N 根绳子进行任意裁剪(不能拼接),请你帮忙计算出这 M 根绳子最长的长度是多少。
第一行包含 2 个正整数 N、M,表示原始绳子的数量和需求绳子的数量。
第二行包含 N 个整数,其中第 i 个整数 Li 表示第 i 根绳子的长度。
输出一个数字,表示裁剪后最长的长度,保留两位小数。
1≤N,M≤100000,
0<Li<109
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in= new Scanner(System.in);
int n=in.nextInt();//绳子的个数
int k=in.nextInt();//将绳子分成几段
int L[]=new int[n];
for (int i=0;i<n; i++)
L[i]=in.nextInt();
double l=0,r=1e9;//设置左边界和右边界
while (r>=l) {
double mid = (l+r)/2;//此时的mid在这里的意思代表剪完之后的绳子的最大值是多少
int sum=0;//在这里sum代表最多能剪掉多少段绳子
for (int i=0;i<n; i++) {
sum+=L[i]/mid;//如果某一段绳子比mid短,就不满足,所以sum=sum+0
//如果大于mid则就看该段绳子能切成几段mid长度的绳子
}
if(sum>=k)//切割的段数太多了,肯定不满足绳子最长的要求,所以要把中值作为左边界从而提高中值(每段绳子)的长度
l=mid;
else//绳子足够长了,但是段数太少了,不满足要求,所以需要把中值作为右边界的值,从而减小中值(每段绳子)的长度
r=mid;
if(Math.abs(r-l)<0.001)
break;
}
System.out.printf("%.2f", r);
}
}
总结
二分法的优点在于可以将需要查找的目标范围不断减半,这样就对时间上进行了极大的优化,对于此类问题我们只需要记住几个步骤:
1.判断二分循环结束的条件。
2.判断循环提前结束的条件。
3.判断左右边界点的变化。
以上的内容为本人在做题的时候进行的总结,如果有不合理的地方还望能够指出,感谢理解和支持