二分法在算法题中的4种常见应用(cont.)

Posted CSU迦叶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分法在算法题中的4种常见应用(cont.)相关的知识,希望对你有一定的参考价值。

目录

1.查找单调序列中是否存在满足某条件的元素

2.寻找序列中第一个(最后一个)满足某条件的元素的位置

3.给定一个定义在[L,R]上的单调函数f(x),求方程f(x)=0的根

4.快速幂的递归和迭代求法


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.)的主要内容,如果未能解决你的问题,请参考以下文章

常见的几个算法

二分查找在NOIP中的运用

算法基础| 二分图解及代码模板

二分查找常见笔试面试题

查找算法

常见的查找算法