二分,二分查找
用于有序,有单调性的问题
有单调性的问题一定可以用二分法解决,反过来,用二分法能解决的问题不一定有单调性。
整数二分——两种模型
一、两种模型基本原理
两种模型,分别用来查找满足条件的左右界。
l:查找的数据左边界
r:查找的数据右边界
A:满足条件的右界
B:满足条件的左界
模型一:
模型二:
二、模型记忆点
1.模型概括
1)左边满足,求满足右界:
mid=l+r +1/2 ; 满足 l=mid ; 不满足 r=mid -1
2)右边 满足,求满足 左界:
mid=l+r/2 ; 满足 r=mid ; 不满足 l=mid +1
2.记忆点
- 记忆点一:
左边 满足,满足时更新数据 左边界 等于mid
右边 满足,满足时更新数据 右边界 等于mid - 记忆点二:
左边满足时,mid取半要 加1,同时不满足的更新对应 减1;
右边满足时,mid取半 不加1,同时不满足的更新对应 加1;
三、模型细节问题思考
- 问题一:为什么mid一个取l+r+1/2,一个取l+r:
int除法只能得int而且是上取整,即2+1/2结果为1,而不是1.5,不是2。
模板一,若取mid=l+r/2,到最后剩2个数时出现问题,如下图剩余两个m,n
mid=l+r的话,mid指向第一个数,如果m满足条件,l=mid,然而一直l=mid,相当于一直不更新,死循环。
- 问题二:更新(或说是循环)的结果是l,r相遇。
l<r是循环条件,while(l<r)
循环最终结局l=r是唯一可能性吗,r可能小于l吗?
如果不可能,应该也可以下边这么写
while(1){
...
if(l=r) break;
}
四、模型的经典代码模板
yxc给出的代码模板
模型一:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
模型二:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
学习到的
l+r>>1中>>表示位运算的“右移”运算,等同于除2,也是向下取整。
如:
int a=11,b; //a二进制为0000 1011
b=a>>2; //b为0000 0101,即为5
五、练习题目
https://www.acwing.com/problem/content/description/791/
我自己用while1, l=r时break写的,结果有错没心情再看了,不知道哪里错:
#include <iostream>
using namespace std;
int find_l(int a[],int x,int l,int r);
int find_r(int a[],int x,int l,int r);
int find_l(int a[],int x,int l,int r){
while(1){
int mid;
mid=l+r>>1;
if(a[mid]>=x) r=mid;
else l=mid+1;
if(l==r) break;
}
if(a[l]==x) return l+1;//最终得出的l是下标,但是求的是第几位所以加1
else return -1;
}
int find_r(int a[],int x,int l,int r){
while(1){
int mid;
mid=l+r+1>>1;
if(a[mid]<=x) l=mid;
else r=mid-1;
if(l==r) break;
}
if(a[l]==x) return l+1;//最终得出的l是下标,但是求的是第几位所以加1
else return -1;
}
int main(){
int n,a[10],b[10],q;
cin>>n>>q;
for(int i=0;i<=n-1;i++) cin>>a[i];
for(int i=0;i<=q-1;i++) cin>>b[i];
for(int i=0;i<=q-1;i++){
cout<<find_l(a,b[i],0,n-1)<<" "<<find_r(a,b[i],0,n-1)<<"\n";
}
return 0;
}
浮点二分
一、基本原理
1.原理类似。
因为不涉及上下取整问题,所以没有取中点需要加加一的问题。
浮点数是线段式的连续,不是整型数的一个一个点,所以更新边界不会加一减一。
2.关于循环条件:因为不断取中点,有可能最终无法达到l=r,有可能l一直小于r,因此循环条件是l<r有可能无法跳出,所以循环条件可以改成l-r的值足够小时,如10^-6。
二、练习题目
求三次方根:https://www.acwing.com/problem/content/792/
#include <iostream>
#include <math.h>
using namespace std;
double f(double a){
double l=0,r,mid;
r=fabs(a);
while(r-l>1e-10){
mid=(l+r)/2.0;
if(mid*mid*mid<=fabs(a)) l=mid;
else r=mid;
}
if(a>=0) return l;
else return -l;
}
int main(){
double a;
cin>>a;
printf("%f\n",f(a));
return 0;
}
学习到的
1.10的几次方怎么写:10的-6次方,1e-6
2.保留5位小数,最后输出时用cout一直报错,改成printf
3.math.h数学库:
取绝对值 fabs(x) 、x的三次方 pow(x)