前言
类似于二分查找,三分查找法也是比较常用的基于分治思想的高效查找方法
三分查找用于左边递增右边递减或者相反的,这一类函数也就是常说的凸函数和凹函数(参考一元二次方程的函数图像)
凸函数或者凹函数总是有一个最大值或者最小值,这样就可以借此判断出三分法中两个中点相对相对于极值的位置
三分搜索的实现主要是判断midl和midr所在值的大小:
以凸函数为例(凹函数类似,只是判mid大小的时候保留小的即可[其实是保留离极值最近的mid]),先以left和right为端点计算出它们的中点midl,然后再以midl和right为端点计算出它们的中点midr,接下来就需要判断f(midl)和f(midr)值的大小了,如果f(midl)大于f(midr),那么说明midl靠近极值,此时令right=midr,否则说明midr靠近极值,此时则令left=midl,总之就是要保留离极值最近的那一个mid,然后重复前面的过程,直到left和right十分接近,最终f(left)就等于极值
确定两个凸点主要有以下两种方法:
确定区间中点作为左中点,接着确定右半区间的中点作为右中点(这里确定左半区间似乎也行,但是大部分人都是取右半区间):
int midl=(l+r)/2;
int midr=(midl+r)/2;
找到这段区间的三分之一长度,接着直接确定左右中点:
int midl=l+(r-l)/3;
int midr=r-(r-l)/3;
三分答案
类似于二分答案,三分查找实际上并不解决有序区间的查找,而是对一个具有唯一最优解的区间进行查找取何值能得到最优性答案
举一个很经典的栗子:
坐标轴x上有若干个点,现在需要从坐标轴x上找一个点,使得所有的点到它的欧几里得距离之和最小
解:
不难发现最优答案的点,左边或者右边一定是小于它的解,最后的距离公式是含有x2项的,那么可以证明这是一个简单的凹(凸)函数模型,即可以用三分查找实现
和二分答案一样,三分的循环有很多写法,下面以凹函数模型为例:
写法一
将每次的答案取最优解,注意这里如果最后取l和r相等的值代入函数不一定能得到正确结果
ll ans=INF;
while(l<r){
int midl=(l+r)/2;
int midr=(midl+r)/2;
ll ans1=solve(midl),ans2=solve(midr);
ans=min(ans,min(ans1,ans2));
if(ans1>ans2) l=midl;
else r=midr;
}
写法二
中间结果不保存只判断,最后得到一个常数区间,那么最后取最优解
while(l+10<r){
int midl=(l+r)/2;
int midr=(midl+r)/2;
if(solve(midl)>solve(midr)) l=midl;
else r=midr;
}
ll ans=INF;
for(int i=l;i<=r;i++)
ans=min(ans,solve(i));
写法三
三分一百次,对中间过程取最优解,精度可以达到1e18
ll ans=INF;
for(int i=1;i<=100;i++){
int midl=(l+r)/2;
int midr=(midl+r)/2;
ll ans1=solve(midl),ans2=solve(midr);
ans=min(ans,min(ans1,ans2));
if(ans1>ans2) l=midl;
else r=midr;
}
浮点数三分
常见的有浮点数区间三分,角度三分等等,模板基本一致(同样以凹函数为例):
const double eps=1e-8;
while (l+eps<r){
double midl=l+(r-l)/3;
double midr=r-(r-l)/3;
if(solve(midl)>solve(midr)) l=midl;
else r=midr;
}
cout<<solve(l)<<endl; //这里l即最优解