目录
二、二分
2.1 整数二分
2.1.1 基本思想
二分的本质:二分就是找到一个性质,可以将一组数据一分为二,使得一边是满足的,另一边是不满足的,二分就是用来寻找这个性质的边界,并且保证性质在区间之内
整数二分就是对整数进行二分,需要小心对应找中间值的处理,小心陷入死循环
二分可以解决单调性的问题,但是没有单调性也不是不可以二分
2.1.2 主要步骤
L r
|———————|———————|
第一种情况:找左边的性质的边界
Step1⭐. 确定分界点:找中间值 mid = (l+r+1)/2 ,然后检查一下这个分界点是否满足左边的性质
Step2、调整区间:如果mid满足,则用 [mid,r], 使 l=mid;
如果不满足,用[l,mid-1], 使 r=mid-1
第二种情况:找右边的性质的边界
Step1⭐、确定分界点:找中间值 mid = (l+r )/2,然后检查一下这个分界点是否满足左边的性质
Step2、调整区间:如果mid满足,则用 [l, mid],r=mid;
如果不满足,用 [mid+1,r],l=mid+1;
//为什么第一种情况中需要补上+1?
例如说,当l = r-1的时候,mid如果只是(l+r)/2,就还是l,如果mid满足条件,更新l=mid,l就还是原来的l,会陷入死循环,所以要加1,让mid算出来是r
//如何判断,更新中间值的时候,需不需要加一⭐
可以在写代码的时候,先不加1,然后 在调整区间的时候,看一下更新的时候,如果 r=mid-1 有减一的操作,就再去中间值更新的地方加上1,如果更新区间的时候,mid+1 或者直接取mid 没有加减操作的,就都可以不用补加一。
Step3、一直到最后,l 会和r 相遇,判断一下l 是不是我们要找的,就完成了。
2.1.3 看个题目
Acwing 789题 数的范围 链接: AcWing 789. 数的范围 - AcWing
像这种查找问题,特别是查找边界的问题,感觉 都会用二分,
这个题的题目描述和解题代码如下:
/*
这个题是需要求输入的数,在数组中的起始位置和结束位置
像这种求边界的问题就 很适合用 二分查找 来解决问题
*/
#include <iostream>
using namespace std; //前两行 不练就会忘记了,嘤嘤嘤
const int N = 100010;
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 =0;
scanf("%d", &x); // x即为这个要求边界的数
int mid;
//先看左边界
int l = 0, r= n-1;
while(l < r){
mid = l + r >> 1;
if(q[mid]>=x){
//如果q[mid] ≥ x,说明,左边界在mid左边或者就是mid
r = mid;
}else{
l = mid+1; //这个地方是+1,所以更新mid的时候不用加1,不会造成死循环
}
}
//最后的时候,l 会跟 r相遇,来判断一下这个时候的情况,如果是x,那就是找到了,如果不是x 那就是数组里都没有x
if(q[l] == x){
cout << l << " ";
}else{
cout << "-1 -1" << endl;
continue;// 在这里就可以跳出这一层了
}
//在判断右边界
l=0, r=n-1;
while(l<r){
mid = l + r +1 >> 1;
if(q[mid] > x){
//如果q[mid]大,说明右边界,在mid左边
r = mid-1; //这个地方减一了 就要记得mid更新的地方+1一下;
}else{
l = mid;
}
}
cout << l << endl; //有左边界就肯定有右边界,所以这里不用更新
}
return 0;
}
2.2 浮点数二分
2.2.1 基本思想
也是找到一个边界,但是 每次都是严格的分成一半,就不需要考虑边界加一,只要保证答案一直在区间之内就行,然后在区间足够小的时候,就认为是一个数了,例如说已经达到10的-8次方了,就认为 l = r
2.2.2 看个题目
求一个数的三次方根,题目要求精度在-6次方,来看代码:
#include <iostream>
using namespace std;
//浮点数的二分问题,看起来比整数二分更加简单
int main(){
double x; // 注意这里的类型是double,
cin >> x;
double l = -100, r = 100; //注意题目给的数的三次方后,有正有负,且答案都在-100 到100 之间,注意区间别再携程0——x了
while(r-l > 1e-8 ){
double mid = (l+r)/2;
if(mid*mid*mid >= x){
r = mid;
}else{
l = mid;
}
}
printf("%lf\n", l); //默认保留6位小数,C语言四舍五入
//%f代表单精度浮点型数据(float),%lf代表双精度浮点型数据(double)。
return 0;
}
来自y总的经验:如果题目要求的精度是-4次,那就算到-6,如果要求-5 就算到-7,总之每次都要多两位小数,这样可以避免误差