基础算法入门03——二分算法
整数二分
对于能采取二分的方法来解决的问题主要需要满足如下条件:
我们能够找到一个性质,使得可以将所有元素分成两部分Left
,Right
,然后我们可以通过二分来获得满足这个性质的边界,或者是不满足这个性质的边界
比如说,对于一般数组的二分查找
1 2 2 4 5 6 6 6 7
如果我们要查找到数字5,那么对于这个有序数组,可以找到一个性质大于等于5
,显然,这个性质是能够将数组分成两份,而我们要查找的5正好就是满足这个性质的一个边界
因为对于数组的下标都是整数,所以边界是有间隙的,也就是有两个index_a
,index_b
而对于查找不同的边界,就会有两套不同的模板,主要是用来避免出现死循环的情况发生
模板1——查找index_a
int binary_search(int l,int r)
{
while(l<r)// 当l=r的时候就是搜索结束了
{
int mid=l+r+1>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
return l;
}
通过mid
将元素先对半分,check()函数
是用来判断mid个
元素是否满足这个性质,从而来判断下一次我们搜索范围该怎么更新(关于l和r的更新),true
时,l=mid
,否则r=mid-1
。
(对于check()的模拟)
模板2——查找index_b
int binary_search(int l,int r)
{
while(l<r)// 当l=r的时候就是搜索结束了
{
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
return l;
}
对于要查找index_b
,和上面的几乎差不多,主要是要看你怎么更新r和l
两个模板的不同之处在于
-
//模板1 mid=l+r+1>>1
这里mid的设置多了个
+1
,原因是在check()==true的时候if(check(q[mid])) r=mid;
因为电脑一般的整数除法是下取整,当r=l+1的时候 mid=(l+r)/2=l,这个时候l更新就会一直更新成l,那么就会陷入死循环,所以我们+1就是帮助他上取整,当r=l+1的时候,l进行更新之后等于r了,就会跳出循环。
(对于check()的模拟)
但是,这两个算法只需要记住一个就够了
使用算法三步走
- 第一步:找到性质,设置
check()
函数 - 第二步:根据
check()
函数的结果来更新r and l
- 第二步:如果
r=mid l=mid+1
,mid设置成l+r>>1
;如果l=mid r=mid-1
,mid设置成l+r+1>>1
题目描述
数的范围
给定一个按照升序排列的长度为 n n n 的整数数组,以及 q q q 个查询。
对于每个查询,返回一个元素 k k k 的起始位置和终止位置(位置从 0 0 0 开始计数)。
如果数组中不存在该元素,则返回 -1 -1
。
输入格式
第一行包含整数 n n n 和 q q q,表示数组长度和询问个数。
第二行包含 n n n 个整数(均在 1 ∼ 10000 1 \sim 10000 1∼10000 范围内),表示完整数组。
接下来 q q q 行,每行包含一个整数 k k k,表示一个询问元素。
输出格式
共 q q q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回 -1 -1
。
数据范围
1
≤
n
≤
100000
1 \le n \le 100000
1≤n≤100000
1
≤
q
≤
10000
1 \le q \le 10000
1≤q≤10000
1
≤
k
≤
10000
1 \le k \le 10000
1≤k≤10000
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
算法分析
对于数列
1 2 2 3 3 4
我们要找元素的起始位置和中止位置
对于找到起始位置,我们可以发现一个性质1
:起始位置右边的数都满足≥x
对于找到中止位置,我们可以发现一个性质2
:中止位置左边的数都满足≤x
对于这两条性质,我们可以分别将数组分成两部分,而我们要找的正是性质的边界,因此我们使用二分算法试试
代码:
#include<iostream>
using namespace std;
const int N=1000010;
int q[N];
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=0;i<n;i++) scanf("%d",&q[i]);
while(m--)
{
int x;
scanf("%d",&x);
int l=0,r=n-1;
while(l<r)//找起始位置
{
int mid=l+r>>1;
if(q[mid]>=x) r=mid;
else l=mid+1;
}
if(q[l]!=x) cout<<"-1 -1"<<endl;
else
{
cout<<l<<" ";
int l=0,r=n-1;
while(l<r)//找终止位置
{
int mid=l+r+1>>1;
if(q[mid]<=x) l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
}
return 0;
}
浮点数二分
和整数二分的区别在于不用考虑陷入死循环的问题,也就是不需要区分l+r+1>>1
比如说求一个数的二次方根,我们要找的二次方根一定满足:0≤t≤x
边界就是[0,x],只有当l和r无限接近的时候,我们才能确定二次方根的值,而不是l==r
#include<iostream>
using namespace std;
int main()
{
double x;
cin>>x;
double l=0,r=x;
while(r-l>1e-6)
{
double mid=(l+r)/2;
if(mid*mid<=x) l=mid;
else r=mid;
}
printf("%.6lf",l);
return 0;
}
# 输入
2
# 输出
1.414213
# 输入
4
# 输出
2.000000