二分法在算法题中的4种常见应用(cont.)
Posted CSU迦叶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分法在算法题中的4种常见应用(cont.)相关的知识,希望对你有一定的参考价值。
目录
3.给定一个定义在[L,R]上的单调函数f(x),求方程f(x)=0的根
1.查找单调序列中是否存在满足某条件的元素
//二分区间为左闭右闭的[l,r],传入的初值为[0,n-1]
int binarySearch(int A[],int l,int r,int x){
int mid;
while(l<=r){
mid = (l+r)/2;//mid为二分区间的中点
if(A[mid]==x)return mid;//找到了x,则返回数组下标
if(A[mid]>x){//中点大于x,则在左子区间[l,mid-1]中查找
r = mid - 1;
}else{//中点小于x,则在右子区间[mid+1,r]中查找
l = mid + 1;
}
}
return -1;
}
int main(){
const int n = 10;
int A[n] = {1,3,4,6,7,8,10,11,12,15};
printf("%d %d\\n",binarySearch(A,0,n-1,4),binarySearch(A,0,n-1,20));//预期输出 2,-1
return 0;
}
注意:
1. 如果序列是递减的,只需将第二个if条件从A[mid]>x改成A[mid]<x
2. 如果二分上界超过int型数据范围的一半,为防止溢出,最好将mid = (l+r)/2改成等价的l+(r-l)/2
3. 这里讨论的情况没有考虑到序列中含有重复元素
4. while条件是l<=r,也就是循环停止时l>r,可以此作为查找元素不存在的判定原则
2.寻找序列中第一个(最后一个)满足某条件的元素的位置
//二分区间为左闭右闭的[l,r],传入的初值为[0,n]
//返回第一个大于等于x的元素的位置
int binarySearch(int A[],int l,int r,int x){
int mid;
while(l<r){
mid = (l+r)/2;//mid为二分区间的中点
if(A[mid]>=x){//中点大于x,则在左子区间[l,mid]中查找
r = mid;
}else{//中点小于x,则在右子区间[mid+1,r]中查找
l = mid + 1;
}
}
return l;
}
int main(){
const int n = 10;
int A[n] = {1,3,3,3,3,8,10,11,12,15};
printf("%d %d\\n",binarySearch(A,0,n,3),binarySearch(A,0,n,10));//预期输出 1,6
return 0;
}
注意:
1. 由于这里不存在查找不到的判定(即使元素不存在,返回的也是“假设其存在,应该在的位置”),因此while条件是l<r,也就是循环终止时l == r,所以最后返回l和r是一样的。
2. 二分的初始区间应该能够覆盖所有可能返回的结果。而这里预查找元素可能是超出数组的上界的,比如说查找16,应该返回的下标是10,所以二分的初始区间应为[0,n]。
3. 至于为什么if条件满足时r = mid,是因为如果if条件满足,说明大于等于x的元素的位置一定在mid左侧或者mid处,如果不满足,说明要找的元素一定在mid右处
//二分区间为左闭右闭的[l,r],传入的初值为[0,n-1]
//返回第一个大于x的元素的位置
int binarySearch(int A[],int l,int r,int x){
int mid;
while(l<r){
mid = (l+r)/2;//mid为二分区间的中点
if(A[mid]>x){//中点大于x,则在左子区间[l,mid]中查找
r = mid;
}else{//中点小于x,则在右子区间[mid+1,r]中查找
l = mid + 1;
}
}
return l;
}
int main(){
const int n = 10;
int A[n] = {1,3,3,3,3,8,10,11,12,15};
printf("%d %d\\n",binarySearch(A,0,n,3),binarySearch(A,0,n,15));//预期输出 5,10
return 0;
}
容易发现,和上一个例子比较,仅仅是把if条件的A[mid]>=x改成了A[mid]>x。
也就是对于问题:寻找有序序列中第一个满足某条件的元素的位置,有一个通用的模板,里面唯一的变量就是某条件。
//解决“寻找有序序列中第一个满足某条件的元素的位置”固定模板
//二分的初始区间需覆盖所有可能解
int binarySearch(int A[],int l,int r,int x){
int mid;
while(l<r){
mid = (l+r)/2;//mid为二分区间的中点
if(某条件){//条件成立,说明第一个满足条件的元素在左子区间[l,mid]中
r = mid;
}else{//条件不成立,说明第一个满足条件的元素在右子区间[mid+1,r]
l = mid + 1;
}
}
return l;//循环终止时l==r,返回l和r是一样
}
该模板适用的问题是,序列从小到大是从不满足到满足再到不满足(等于x的元素),或者是从不满足到满足(大于等于x的元素)。对于前一种情况,可能还会要求查找最后一个满足条件的元素。那么可以将其转化为查找第一个大于x的元素,然后将得到的下标减1。
3.给定一个定义在[L,R]上的单调函数f(x),求方程f(x)=0的根
1和2所说是正数情况下的二分查询问题,但是二分的应用远不止如此。
例:求解根号2的值(精确到1e-5)。
该问题可以转化为求解方程f(x)=x^2-2=0根,代码如下
const double eps = 1e-5;
double f(double x){
return x*x-2;
}
double calSqrt(double l,double r){
double mid;
while(r-l>eps){//即l<r-eps,表明了精度要求
mid = (l+r)/2;
if(f(mid)>0){//mid>sqrt(2)
r = mid;//向左子区间逼近
}else{//mid<sqrt(2)
l = mid;//向右子区间逼近
}
}
return mid;//mid即为f(x)=0的根
}
1. f(x)可以换成别的单调函数,如果是单调递减函数,将if条件中的>改成<即可。
4.快速幂的递归和迭代求法
未完待续
虽然认识得还很肤浅,但是隐隐感受到一切以单调为背景,找一个特殊的分水岭的问题,都有二分法的身影在里面。而满足这样条件的问题是很多的。
以上是关于二分法在算法题中的4种常见应用(cont.)的主要内容,如果未能解决你的问题,请参考以下文章