一、相关概念
定义:二分查找算法也称折半搜索算法,在有序数组中查找某一特定元素的搜索算法。
这种搜索算法每一次比较都使搜索范围缩小一半,时间复杂度是log(n)
条件:①必须是有序数组
②对某一特定元素查找
二、二分算法的具体过程
mid=(l+r)/2; // 尽可能将mid向左边取
二分搜索:通过计算中间坐标mid的元素大小a[mid]并与查找元素大小x做比较
①若a[mid]>=x,则查找元素在mid的左边[l,mid]
②若a[mid]<x 则查找元素在mid右边[mid+1,r]
当 l==r 时退出二分操作,即 l r 为查找元素的下标
模板例题:
对一组有序的数组,{1,5,6,11,15,18,21},请找出元素6对应数组的下标
第一次二分操作:
第二次二分操作:
第三次二分操作:
附上代码:
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
int a[1001],n,x;
int main()
{
scanf("%d%d",&n,&x);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int l=1,r=n;
while(l<r)
{
int mid=(l+r)/2;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
printf("%d",l);
return 0;
}
三、关于二分算法时间复杂度的分析
遇到查找的问题,可以从[1,n]遍历一遍挨个判断查找,这种方法的时间复杂度为O(n),有着许多不必要的操作,使用二分算法将大大减少了时间复杂度,转化为O(logn),从而优化时间。
缺陷:二分算法的实现必须依赖有序数组,否则无法通过二分查找
四、二分写法
1.在单调递增序列查找某个元素或者其后继
仍然是上面的例子对于一组数{1,5,6,11,15,18,21},查找元素大小7的后继(即大于7的第一个元素),不难发现,区别在于第三次二分操作,代码同上
2.在单调递增序列查找某个元素或者其前缀
仍然是上面的例子对于一组数{1,5,6,11,15,18,21},查找元素大小7的前缀
同理,只需要将上述代码修改
在check判断时,
if(a[mid]>=x) r=mid; else l=mid+1;
改成
if(a[mid]<=x) l=mid;else r=mid-1;
然而此时要注意: mid=(l+r+1)/2; //即将mid尽量往上取,超过目标数字,避免死循环
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
int a[1001],n,x;
int main()
{
scanf("%d%d",&n,&x);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int l=1,r=n;
while(l<r)
{
int mid=(l+r+1)/2;
if(a[mid]<=x) l=mid;
else r=mid-1;
}
printf("%d",l);
return 0;
}
以上两种二分写法均可查找某一元素在数组中的下标
3.当某一元素重复出现,找出该元素的左边界和右边界的下标
上面讲述了两种二分写法
写法一
mid=(l+r)/2; // 尽可能将mid往下取,则求出左边界
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
int a[1001],n,x;
int main()
{
scanf("%d%d",&n,&x);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int l=1,r=n;
while(l<r)
{
int mid=(l+r)/2;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
printf("%d",l);
return 0;
}
写法二
mid=(l+r+1)/2; // 尽可能将mid往上取,即求出右边界
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
int a[1001],n,x;
int main()
{
scanf("%d%d",&n,&x);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int l=1,r=n;
while(l<r)
{
int mid=(l+r+1)/2;
if(a[mid]<=x) l=mid;
else r=mid-1;
}
printf("%d",l);
return 0;
}
五、浮点型二分
浮点型二分不需要考虑边界,往往是在一个精度范围,在精度范围内寻找答案
double eps=1e-5;
while(r-l>eps)
{
double mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid;
}
例题1.银行贷款 P1163- 银行贷款
思路:通过二分运算,寻找答案,判断答案的正确性
对本题的补充解释:
对于还款,每次还款应当先减去当月的利息,再将剩余的钱还款至本金当中(先还当月利息,再还本金)
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
int n,a,b;
double w;
double eps=1e-5;
int check(double x,int q1)
{
double q=(double)q1;
for(int i=1;i<=b;i++)
{
double x1=q*x; //当月的利息 (本金*利率)
q=q-(a*1.0-x1); // 当月剩余本金 (本金-(还钱金额-当月利息))
}
if(q>0) return 1;
else return 0;
}
int main()
{
scanf("%d%d%d",&n,&a,&b);
double l=0,r=3.0;
w=a*b-n;
while(r-l>eps)
{
double mid=(l+r)/2;
if(check(mid,n)) r=mid;
else l=mid;
}
printf("%.1lf",l*100);
return 0;
}