前置知识:
二分,函数(数学领域)。
二分
首先二分的能解决的,仅仅是 单调函数 求极值。什么叫做单调函数?即 随 单调不减 或 单调不增 均可用二分解决。如图:
上图是函数 ,显然可以用二分在函数上进行解决问题。
实际上二分的解决领域仅仅是 类型的函数,二次及以上都不行了,反比例函数也不行了。
三分
辟谣
请不要以为我在开玩笑!确实有这样一个算法。
很多初学者会这样认为:
二分就是在 找到中点 ,把搜索范围每次减小一半。
三分就是在 找到三等分点 ,把搜索范围每次变为 。
时间复杂度都是 级别的(只不过 和 ),没有什么意义。
按照这个方法,我可以写出 分,写出 ,不过复杂度还是 ,都一样的,好难写!
三分不是你所谓的三分,它有一定的字面意思,但绝对不是这个意思!
二分和三分
首先我们来看一个函数:
如图为
的二次函数,取名为
.
一个问题: 时 的最小值为?(经典的单谷函数求最小值题)
假设我们二分,看是否能得到结果?能算下去吗?
第一步, ,则 .
此时你怎么操作呢?是 还是 呢?
你不知道你取到的中点是在答案左边还是在答案右边,这样你肯定求不出来了。
所以,这时,我们要开始了解三分了!
三分的实现
对于 ,首先找到 和 (两个三等分点),把 和 进行比较。
当求的是最小值时: ,则 ;否则 .
当求的是最大值时: ,则 ;否则 .
如何理解呢?总之就是 离答案越近的就保留,离答案相对远的则作为下一次三分的端点。
还是那个题目:
求 , 时的 .
手算一下:
可得 时 取到最小值为 .
这个结果将用于检验我们的三分过程。(这里三分保留 位小数方便手算)
第一步 ,你发现 更接近,于是 .
第二步 ,你发现 更接近,于是 .
第三步 ,你发现
贴一下程序计算的结果:(每次输出 的值)
/*
程序保留了 11 位小数的精度,本题只需要 6 位精度
-5 5
-5 1.66667
-2.77778 1.66667
-1.2963 1.66667
-1.2963 0.679012
-1.2963 0.0205761
-0.857339 0.0205761
-0.857339 -0.272062
-0.662247 -0.272062
-0.662247 -0.402124
-0.575539 -0.402124
-0.575539 -0.459929
-0.537002 -0.459929
-0.537002 -0.48562
-0.519875 -0.48562
-0.508456 -0.48562
-0.508456 -0.493232
-0.503382 -0.493232
-0.503382 -0.496615
-0.503382 -0.498871
-0.501878 -0.498871
-0.500876 -0.498871
-0.500876 -0.499539
-0.50043 -0.499539
-0.50043 -0.499836
-0.500232 -0.499836
-0.5001 -0.499836
-0.5001 -0.499924
-0.500041 -0.499924
-0.500041 -0.499963
-0.500015 -0.499963
-0.500015 -0.499981
-0.500015 -0.499992
-0.500008 -0.499992
-0.500008 -0.499997
-0.500004 -0.499997
-0.500002 -0.499997
-0.500002 -0.499999
-0.500001 -0.499999
-0.500001 -0.5
-0.5 -0.5
*/
最后得到 为最小值,计算即可。
是不是很妙?
代码实现
这里有一些细节。
你不能直接写 l = r
,那样你肯定会因为奇怪的精度问题而陷入死循环。
所以,我们应当设置一个极小的常数为 , 即认为 ,可以用 的精度来调整三分的精度。 本题 已足够。当然只要在计算机能承受的精度之内 就没有问题(最多 ~ )了。
关于设置极小值的问题,越小越好,只要不超过计算机能承受的范围(不是指
的范围啊)即可,一般 -0x7fffffff
是简单粗暴的操作。当然 -1e16
也是可以的。总之不要因为最小值耽误了整个程序的正误啊!
最后贴一个模板(伪代码):
while(l + (1e-11) < r) { //这里写的是单谷函数求最小值
lmid = l + (r-l) / 3;
rmid = r - (r-l) / 3;
if(calc(lmid) <= calc(rmid)) r = rmid;
else l = lmid;
}
/* */
while(l + (1e-11) < r) { //这里写的是单峰函数求最大值
lmid = l + (r-l) / 3;
rmid = r - (r-l) / 3;
if(calc(lmid) >= calc(rmid)) r = rmid;
else l = lmid;
}
掌握了模板,可以去写一些板子题啦!