三分法模板講解

三分查找

我們都知道 二分查找 適用於單調函數中逼近求解某點的值。
如果遇到凸性或凹形函數時,可以用三分查找求那個凸點或凹點。
下面的方法應該是三分查找的一個變形。

如圖所示,已知左右端點L、R,要求找到白點的位置。
思路:通過不斷縮小 [L,R] 的範圍,無限逼近白點。
做法:先取 [L,R] 的中點 mid,再取 [mid,R] 的中點 mmid,通過比較 f(mid) 與 f(mmid) 的大小來縮小範圍。
當最後 L=R-1 時,再比較下這兩個點的值,我們就找到了答案。
1、當 f(mid) > f(mmid) 的時候,我們可以斷定 mmid 一定在白點的右邊。
反證法:假設 mmid 在白點的左邊,則 mid 也一定在白點的左邊,又由 f(mid) > f(mmid) 可推出 mmid < mid,與已知矛盾,故假設不成立。
所以,此時可以將 R = mmid 來縮小範圍。
2、當 f(mid) < f(mmid) 的時候,我們可以斷定 mid 一定在白點的左邊。
反證法:假設 mid 在白點的右邊,則 mmid 也一定在白點的右邊,又由 f(mid) < f(mmid) 可推出 mid > mmid,與已知矛盾,故假設不成立。
同理,此時可以將 L = mid 來縮小範圍。

int SanFen(int l,int r) //找凸點  
{  
    while(l < r-1)  
    {  
        int mid  = (l+r)/2;  
        int mmid = (mid+r)/2;  
        if( f(mid) > f(mmid) )  
            r = mmid;  
        else  
            l = mid;  
    }  
    return f(l) > f(r) ? l : r;  
}  

三分查找

一. 概念
在二分查找的基礎上,在右區間(或左區間)再進行一次二分,這樣的查找算法稱爲三分查找,也就是三分法。
三分查找通常用來迅速確定最值。

二分查找所面向的搜索序列的要求是:具有單調性(不一定嚴格單調);沒有單調性的序列不是使用二分查找。
與二分查找不同的是,三分法所面向的搜索序列的要求是:序列爲一個凸性函數。通俗來講,就是該序列必須有一個最大值(或最小值),在最大值(最小值)的左側序列,必須滿足不嚴格單調遞增(遞減),右側序列必須滿足不嚴格單調遞減(遞增)。如下圖,表示一個有最大值的凸性函數:

二、算法過程

1)、與二分法類似,先取整個區間的中間值midmid = (left + right) / 2;  
(2)、再取右側區間的中間值midmid,從而把區間分爲三個小區間。
midmid = (mid + right) / 2;  
(3)、我們mid比midmid更靠近最值,我們就捨棄右區間,否則我們捨棄左區間?。
比較mid與midmid誰最靠近最值,只需要確定mid所在的函數值與midmid所在的函數值的大小。當最值爲最大值時,mid與midmid中較大的那個自然更爲靠近最值。最值爲最小值時同理。

if (cal(mid) > cal(midmid))  
    right = midmid;  
else  
    left = mid; 

(4)重複(1)(2)(3)直至找到最值。

(5)另一種三分寫法
double three_devide(double low,double up) {  
    double m1,m2;  
    while(up-low>=eps)  {  
        m1=low+(up-low)/3;  
        m2=up-(up-low)/3;  
        if(f(m1)<=f(m2))  
            low=m1;  
        else  
            up=m2;  
    }  
    return (m1+m2)/2;  
}  

算法的正確性:
1、mid與midmid在最值的同一側。由於凸性函數在最大值(最小值)任意一側都具有單調性,因此,mid與midmid中,更大(小)的那個 數自然更爲靠近最值。此時,我們遠離最值的那個區間不可能包含最值,因此可以捨棄。
2、mid與midmid在最值的兩側。由於最值在中間的一個區間,因此我們捨棄一個區間後,並不會影響到最值

const double EPS = 1e-10;  

double calc(double x)  {  
    // f(x) = -(x-3)^2 + 2;  
    return -(x-3.0)*(x-3.0) + 2;  
}  

double ternarySearch(double low, double high)  {  
    double mid, midmid;  
    while (low + EPS < high)  {  
        mid = (low + high) / 2;  
        midmid = (mid + high) / 2;  
        double mid_value = calc(mid);  
        double midmid_value = calc(midmid);  
        if (mid_value > midmid_value)  
            high = midmid;  
        else  
            low = mid;  
    }  
    return low;  
}  

調用ternarySearch(0, 6),返回的結果爲3.0000
二分法作爲分治中最常見的方法,適用於單調函數,逼近求解某點的值。但當函數是凸性函數時,二分法就無法適用,這時三分法就可以“大顯身手”~~

程序模版如下:

double Calc(Type a){
    /* 根據題目的意思計算 */
}
void Solve(void){
    double Left, Right;
    double mid, midmid;
    double mid_value, midmid_value;
    Left = MIN; Right = MAX;
    while (Left + EPS < Right){
        mid = (Left + Right) / 2;
        midmid = (mid + Right) / 2;
        mid_area = Calc(mid);
        midmid_area = Calc(midmid);
        // 假設求解最大極值.
        if (mid_area >= midmid_area) Right = midmid;
        else Left = mid;
    }
}

現根據幾道的OJ題目來分析三分法的具體實現。
buaa 1033 Easy Problem
http://acm.buaa.edu.cn/oj/problem_show.php?c=0&p=1033

題意爲在一條線段上找到一點,與給定的P點距離最小。很明顯的凸性函數,用三分法來解決。
Calc函數即爲求某點到P點的距離。
ZOJ 3203 Light Bulb
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3203

如圖,人左右走動,求影子L的最長長度。
根據圖,很容易發現當燈,人的頭部和牆角成一條直線時(假設此時人站在A點),此時的長度是影子全在地上的最長長度。當人再向右走時,影子開始投影到牆上,當人貼着牆,影子長度即爲人的高度。所以當人從A點走到牆,函數是先遞增再遞減,爲凸性函數,所以我們可以用三分法來求解。

下面只給出Calc函數,其他直接套模版即可。

double Calc(double x){
    return (h * D - H * x) / (D - x) + x;
}

heru 5081 Turn the corner 08年哈爾濱regional網絡賽
http://acm.hrbeu.edu.cn/index.php?act=problem&id=1280

汽車拐彎問題,給定X, Y, l, d判斷是否能夠拐彎。首先當X或者Y小於d,那麼一定不能。
其次我們發現隨着角度θ的增大,最大高度h先增長後減小,即爲凸性函數,可以用三分法來求解。

這裏的Calc函數需要比較繁瑣的推倒公式:
s = l * cos(θ) + w * sin(θ) - x;
h = s * tan(θ) + w * cos(θ);
其中s爲汽車最右邊的點離拐角的水平距離, h爲裏拐點最高的距離, θ範圍從0到90。
POJ 3301 Texas Trip
http://acm.pku.edu.cn/JudgeOnline/problem?id=3301

題意爲給定n(n <= 30)個點,求出飽含這些點的面積最小的正方形。

有兩種解法,一種爲逼近法,就是每次m分角度,求出最符合的角度,再繼續m分,如此進行times次,即可求出較爲精確的解。(m 大概取10, times取30即可)

第二種解法即爲三分法,首先旋轉的角度只要在0到180度即可,超過180度跟前面的相同的。座標軸旋轉後,座標變換爲:
X’ = x * cosa - y * sina;
y’ = y * cosa + x * sina;

至於這題的函數是否是凸性的,爲什麼是凸性的,我也無法給出準確的證明,希望哪位路過的大牛指點一下~~
例題更新(2010.5.5)
hdu 3400 Line belt
http://acm.hdu.edu.cn/showproblem.php?pid=3400
典型的三分法,先三分第一條線段,找到一個點,然後根據這個點再三分第二條線段即可,想出三分的思路基本就可以過了。
對於求解一些實際問題,當公式難以推導出來時,二分、三分法可以較爲精確地求解出一些臨界值,且效率也是令人滿意的。
(轉自http://hi.baidu.com/czyuan_acm/blog/item/8cc45b1f30cefefde1fe0b7e.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章