1. 程式人生 > >三分法求單峰(單谷)函式極值

三分法求單峰(單谷)函式極值

what is 三分法

對於二分,相信你一定十分熟悉。就是在一個具有單調性序列上查詢你所需要的數字。由於其單調性,你每一次在查詢是就可以將規模縮小一半,大致就是:
1.假設這個數列單調遞增
2.維護一個區間左端點\(l\),區間右端點r和中間點\(mid\)
3.如果\(mid\)比想要的值小,則左邊肯定不可以,那麼\(l=mid\)
2.如果\(mid\)比想要的值大,則右邊肯定不可以,那麼\(r=mid\)
因此大致就可以這麼寫:

while (l+1<r)
{
    int mid=l+r>>1;
    if (v<a[mid) r=mid;
    else l=mid;
}

不保證程式碼正確,但是具體思想就是這樣。
三分也一樣啊:
對於一段拋物線(極值的一邊單調遞增,極值的一邊單調遞減)我們就可以把它分成三段,根據其影象特性來求解。


三分法求二次函式峰值

對於三分,我們用左端點\(lmid\)\(rmid\)進行維護,將這個影象分成三段。並且影象區間的左右端點分別是\(l<r.\)則我們可以選擇這麼考慮:(以二次函式\(y=-5x^{2}+8x-1\)為例)
如圖所示:
函式影象
\(lmid\)處於\(A\)點,\(rmid\)處於\(B\)點時:可將左端點\(l\)縮到\(lmid\),右端點不變以保證極值存在。
\(lmid\)處於\(A\)點,\(rmid\)

處於\(C\)點時:照樣可以將\(l\)縮到\(lmid\)
同理,
\(lmid\)處於\(C\)點,\(rmid\)處於\(D\)點時:可將右端點\(r\)縮到\(rmid\),左端點不變以保證極值存在。
\(lmid\)處於\(B\)點,\(rmid\)處於\(B\)點時:照樣可以將\(r\)縮到\(rmid\)
故得到結論:
\[f(l)<f(r)→l=lmid\]
\[f(l)≥f(r)→r=rmid\]
然後就進行簡單的程式碼實現:

#include<bits/stdc++.h>
using namespace std;
double a,b,c;
inline double f(double x) {
    return a*x*x+b*x+c;
}
int main(void)
{
    cin>>a>>b>>c;
    //形如y=ax^2+by+c的二次函式
    double l=-1e9,r=1e9;
    while (l+1e-9<r)
    {
        double lmid=l+(r-l)/3.0;
        //影象上位於1/3部分的靠左的mid值 
        double rmid=l+(r-l)/3.0*2.0;
        //影象上位於2/3部分的靠右的mid值
        if (f(lmid)<f(rmid)) l=lmid;
        else r=rmid;
        //求單峰極值 
    } 
    cout<<"X="<<l<<'\n';
    cout<<"Y="<<f(l); 
} 

二次函式求單谷谷值 & 高次函式應用

通過畫圖和分類討論\(a<0\)的情況,不難得出:
\[f(l)>f(r)→l=lmid\]
\[f(l)≤f(r)→r=rmid\]
程式碼實現只要if內反一下即可:

#include<bits/stdc++.h>
using namespace std;
double a,b,c;
inline double f(double x) {
    return a*x*x+b*x+c;
}
int main(void)
{
    cin>>a>>b>>c;
    //形如y=ax^2+by+c的二次函式
    double l=-1e9,r=1e9;
    while (l+1e-9<r)
    {
        double lmid=l+(r-l)/3.0;
        //影象上位於1/3部分的靠左的mid值 
        double rmid=l+(r-l)/3.0*2.0;
        //影象上位於2/3部分的靠右的mid值
        if (f(lmid)>f(rmid)) l=lmid;
        else r=rmid;
        //求單峰極值 
    } 
    cout<<"X="<<l<<'\n';
    cout<<"Y="<<f(l); 
} 

如果需要高次函式過其它影象,只要在f內稍作修改即可。