二分法扩展

二分查找:点击这里
二分查找扩展:点击这里

上面讲解的都是整数情况下的二分查询问题,事实上二分法的应用远不止此,下面介绍几个相关的例子。

【计算√2 的近似值】

  • 首先介绍如何计算√2 的近似值

  • 对 f(x) = x2 来说,在 x∈[1, 2] 范围内,f(x) 是随着x的增大而增大的,这就给使用二分法创造了条件,即可以采用如下策略来逼近√2的值。(注,由于√2是无理数,因此只能获得它的近似值,这里不妨以精确到10-5为例)。

  • 令浮点型 left 和 right 的初值分别为1和2,然后根据 left 和 right 的中点 mid 处 f(x) 的值与2的大小来选择子区间进行逼近:

  1. 如果 f(mid) > 2,说明 mid > √2 ,应当在 [left, mid] 的范围内继续逼近,故令 right = mid
    在这里插入图片描述
  2. 如果 f(mid) < 2,说明 mid < √2,应当在 [mid, right] 的范围内继续逼近,故令 left = mid
    在这里插入图片描述
  3. 上面两个步骤当 right - left < 10-5 时结束。显然当 left 与 right 的距离小于10-5 时已经满足精度要求,mid 即为所求的近似值。

可以得到如下代码,其中 eps 为精度,le-5 即为10-5

const double eps = 1e-5;

double f(double x)	//计算f(x) 
{
	return x*x;
}

double calSqrt()
{
	double left = 1,right = 2, mid;	//[left,right] = [1,2] 
	while(right-left>eps)
	{
		mid = (left+right)/2;	//取left与right的中点 
		if(f(mid)>2)	//mid>sqrt(2) 
			right = mid;	//往左子区间[left,mid]继续逼近 
		else			//mid<sqrt(2) 
			left = mid;		//往右子区间[mid,right]继续逼近 
	}
	return mid;	//返回mid即为sqrt(2)的近似值 
} 

【求方程 f(x)=0 的根】

  • 事实上,计算 √2 的近似值的问题其实是这样一个问题的特例:给定一个定义在 [L, R] 上的单调函数 f(x),求方程 f(x)=0 的根
  • 同样,假设精度要求为 eps = 10-5,函数 f(x) 在 [L, R] 上递增,并令 left 与 right 的初值分别为L、R,然后就可以根据 left 与 right 的中点 mid 的函数值 f(mid) 与 0 的大小关系来判断应当往哪个子区间继续逼近 f(x)=0 的根:
  1. 如果 f(mid)>0,说明 f(x) = 0 的根在mid左侧,应往左子区间 [left, mid] 继续逼近,即令 right = mid。
    在这里插入图片描述
  2. 如果 f(mid)<0,说明 f(x) = 0 的根在 mid 右侧,应往右子区问 [mid, right] 继续逼近,即令 left = mid。
    在这里插入图片描述
  3. 上面的步骤当 right - left < 10-5 时表明达到精度要求,结束算法,所返回的当前 mid 值即为f(x)=0 的根。

由此可以写出相应的二分法代码:

const double eps = 1e-5;

double f(double x)	//计算f(x) 
{
	return ...;
}

double solve(double L,double R)
{
	double left = L,right = R, mid;	//[left,right] = [L,R] 
	while(right-left>eps)
	{
		mid = (left+right)/2;	//取left与right的中点 
		if(f(mid)>0)	
			right = mid;	//往左子区间[left,mid]继续逼近 
		else			
			left = mid;		//往右子区间[mid,right]继续逼近 
	}
	return mid;	//返回mid即为f(x)=0的根 
} 
  • 显然,计算 √2 的近似值等价于求 f(x) = x2 - 2 = 0 在 [1, 2] 范围内的根。
  • 另外,如果 f(x) 递减,只需要把代码中的 f(mid) > 0改为 f(mid) < 0 即可,此处不再重复给出代码。

【装水问题】

  • 再来看一个装水问题。有一个侧面看去是半圆的储水装置,该半圆的半径为 R,要求往里面装入高度为 h 的水,使其在侧面看去的面积与半圆面积的比例恰好为 r ,如图所示。现在给定 R 和 r,求高度 h
    在这里插入图片描述
  • 在这个问题中,需要寻找水面高度 h 与面积比例 r 之间的关系。而很显然的是,随着水面升高,面积比例 r 一定是增大的
  • 由此可以得到这样的思路:在 [0, R] 范围内对水面高度 h 进行二分,计算在高度下面积比例 r 的值。
  1. 如果计算得到的 r 比给定的数值要大,说明高度过高,范围应缩减至较低的一半;
  2. 如果计算得到的 r 比给定的数值要小,说明高度过低,范围应缩减至较高的一半。
  3. 至于 h 与 r 的关系式的推导,在这里不讨论,读者应当能自行计算出函数式 r = f(h)。

根据上面的思路,可以写出下面的代码:

#include<cstdio>
#include<cmath>
const double PI = acos(-1.0);
const double eps = 1e-5;

double f(double R, double h)	//计算r = f(h),由实际关系可知r关于h递增 
{
	double alpha = 2*acos( (R-h)/R );
	double L = 2*sqrt( R*R-(R-h)*(R-h) );
	double S1 = alpha*R*R/2 - L*(R-h)/2;
	double S2 = PI*R*R/2;
	return S1/S2;
}

double solve(double R,double r)
{
	double left = 0,right = R, mid;	//[left,right] = [0,R] 
	while(right-left>eps)
	{
		mid = (left+right)/2;	//取left与right的中点 
		if(f(R,mid)>r)	
			right = mid;	//往左子区间[left,mid]继续逼近 
		else			
			left = mid;		//往右子区间[mid,right]继续逼近 
	}
	return mid;	//返回mid即为所求的水面高度h 
}

int main()
{
	double R,r;
	scanf("%lf%lf",&R,&r);
	printf("%.4f\n",solve(R,r));
	return 0;
}

【木棒切割问题】

  • 接着来看木棒切割问题:给出 N 根木棒,长度均已知,现在希望通过切割它们来得到至少 K 段长度相等的木棒(长度必须是整数),问这些长度相等的木棒最长能有多长

  • 例如对三根长度分别为10、24、15 的木棒来说,假设 K= 7,即需要至少 7 段长度相等的木棒,那么可以得到的最大长度为 6,在这种情况下,第一根木棒可以提供 10/6 = 1 段、第二根木棒可以提供 24/6 = 4 段、第三根木棒可以提供 15/6 = 2 段,达到了 7 段的要求。

  • 对这个问题来说,首先可以注意到一个结论:如果长度相等的木棒的长度 L 越长,那么可以得到的木棒段数 k 越少

    扫描二维码关注公众号,回复: 5783513 查看本文章
  • 从这个角度出发便可以想到本题的算法,即二分答案(最大长度L),根据对当前长度 L 来说能得到的木棒段数 k 与 K 的大小关系来进行二分。由于这个问题可以写成求解最后一个满足条件 “k ≥ K" 的长度L,因此不妨转换为求解第一个满足条件 “k<K” 的长度L,然后减1 即可。这个思路用 二分查找扩展里介绍的模板 便能解决,代码自己完成。

显然,木棒切割问题和前面的装水问题都属于二分答案的做法,即对题目所求的东西进行二分,来找到一个满足所需条件的解。

最后留一个问题思考:给出 N 个线段的长度, 试将它们头尾相接(顺序任意)地组合成一个凸多边形,使得该凸多边形的外接圆 (即能使凸多边形的所有顶点都在圆周上的圆) 的半径最大,求该最大半径。其中 N 不超过105,线段长度均不超过100,要求算法中不涉及坐标的计算。

猜你喜欢

转载自blog.csdn.net/qq_42815188/article/details/88881055