凸多邊形的極值點

前言

在計算幾何中,我們經常需要尋找一個2D多邊形(Polygon)的極值點。例如通過x\y的極值點,我們可以定義一個多邊形的包圍盒。更一般的情況下,我們可能會需要尋找一個多邊形在任意方向上的極值點。對於n個點的點集,很容易找到一個O(n)的算法,只需要依次測試每個點即可。然而對於凸多邊形,可以利用二分搜索的思想在O(log(n))時間內找到極值點。首先,定義一個任意的方向向量u,接下來我們詳細講解該算法的思路

平面點集和簡單多邊形的凸包在這兩篇博文中已經介紹過了:平面點集的凸包算法多邊形快速凸包算法。因此,對於平面點集和任意簡單多邊形只需要先計算凸包,然後再計算凸包極值點即可。其算法複雜度由凸包算法決定。

凸包極值點算法

首先,定義2D凸多邊形S由n個點構成V0,V1,...,Vn-1,Vn=V0,並且沿着逆時針方向給定。

定義e_i爲第i條邊(從Vi到Vi+1),\mathbf{ev_i}=V_{i+1}-V_i爲邊向量。

目標:找到S中的所有點在u方向上的極值點。也就是將點投影在u說定義的直線上,極值點就是投影點的極值點,如下圖所示:

可以看到,相對於u,Vi在Vj之上。也就是(Vi-Vj)與u之間形成一個銳角,相當於下面的幾何條件:

\mathbf{u}*(V_i-V_j)>0

這個條件可以幫助我們判斷,一條邊e_i相對於u的投影是在增加還是減少。

暴力算法

最直接的辦法就是暴力搜索每個點,每一步與當前保存的極值點比較。該算法的僞代碼如下:

Input: W = {V0,V1,...,Vn-1}  n個點的點集
       u = 給定的方向向量
記 max = min = 0。
for each point Vi in {V1,...,Vn-1}  (i=1,n-1)
{
    if (u · (Vi - Vmax) > 0) {  // Viis above the prior max
        max = i;                // new max index = i
        continue;               // a new max can't be a new min
    }
    if (u · (Vi - Vmin) < 0)    // Vi is below the prior min
        min = i;                // new min index = i
}

return max = 最大值點的Index
       min = 最小值點的Index

儘管算法是線性的,但還可以通過二分搜索加快。

二分搜索

對於一個凸多邊形的點集,我們可以通過二分搜索,在O(log(n))時間內找到極值點。

首先,假設極大值點在頂點Va和Vb之間,Vc爲Va和Vb的中間點,爲了實現二分搜索,我們需要將下一步的搜索範圍縮小到[a, c]或者[c, b]。由於多邊形是凸的,我們可以通過比較A、C處的邊向量\mathbf{ev}_a\mathbf{ev}_c來做到這一點。下面詳細說明:

如上圖所示。假定u向上,A、B、C的相對位置可能有6種情況。對於每種情況,要麼[a, c],要麼[c, b]包含最大值點。這是因爲,多邊形Su方向上是單調的,它由兩個單調鏈組成,從最小值點開始,到最大值點單調遞增,然後再單調遞減回到最小值點。而且無論u的方向如何,這個結論都是成立的。

接下來,我們就能構造二分搜索了。從[a=0,b=n]開始,c=\left \lfloor (a+b)/2 \right \rfloor,在每一步,我們需要確定是將a增加到c,還是b減小到c,將包含最大值的區間縮小一半。當然,我們需要檢查,是否已經找到了一個局部極大值點Vc,這只需要檢查\mathbf{ev}_{c-1}\cdot \mathbf{u}\mathbf{ev}_{c}\cdot \mathbf{u}的符號是否一致即可。如果兩者的符號不一致,說明Vc是局部極大值點,同時也是多邊形的全局最大值點。

於是,我們得到,尋找相對於u的最大值點的僞代碼如下:

Input: OMEGA = {V0,V1,...,Vn-1,Vn=V0} is a 2D proper convex polygon
       u = a 2D direction vector

if V0 is a local maximum, then return 0;
Put a=0; b=n;
Put A = the edge vector at V0;
forever {
    Put c = the midpoint of [a,b] = int((a+b)/2);
    if Vc is a local maximum, then it is the global maximum
        return c;

    // no max yet, so continue with the binary search
    // pick one of the two subchains [a,c] or [c,b]
    Put C = the edge vector at Vc;
    if (A points up) {
        if (C points down) {
            b = c;                     select [a,c]
        }
        else C points up {
            if Va is above Vc {
                 b = c;                 select [a,c]
            }
            else Va is below Vc {
                 a = c;                 select [c,b]
                 A = C;
            }
        }
    }
    else A points down {
        if (C points up) {
            a = c;                     select [c,b]
            A = C;
        }
        else C points down {
            if Va is below Vc {
                 b = c;                 select [a,c]
            }
            else Va is above Vc {
                 a = c;                 select [c,b]
                 A = C;
            }
        }
    }
    if (b <= a+1) then the chain is impossibly small
        return an error; since something's  wrong
}

 

下面是二分搜索算法的一個C++實現:

// Assume that classes are already given for the objects:
//    Point and Vector (2D) with:
//        coordinates {float x, y;}
//        operators for:
//            == to test equality
//            != to test inequality
//            =  for assignment
//            -Vector for unary minus
//            Point  = Point ± Vector
//            Vector = Point - Point
//            Vector = Vector ± Vector
//    Line with defining points {Point P0, P1;}
//===================================================================


// dot product (2D) which allows vector operations in arguments
#define dot(u,v)   ((u).x * (v).x + (u).y * (v).y)

// tests for vector orientation relative to a direction vector u
#define up(u,v)         (dot(u,v) > 0)
#define down(u,v)       (dot(u,v) < 0)
#define dr(u,Vi,Vj)     (dot(u, (Vi)-(Vj))) // direction sign of (Vi-Vj)
#define above(u,Vi,Vj)  (dr(u,Vi,Vj) > 0)   // true if Vi is above Vj
#define below(u,Vi,Vj)  (dr(u,Vi,Vj) < 0)   // true if Vi is below Vj
 

// polyMax_2D(): find a polygon's max vertex in a specified direction
//    Input:  U   = a 2D direction vector
//            V[] = array vertices of a proper convex polygon
//            n   = number of polygon vertices, with V[n]=V[0]
//    Return: index (>=0) of the maximum vertex, or
//            (-1) = an error [Note: should be impossible, but...]
int
polyMax_2D( Vector U, Point* V, int n )
{
    if (n < 10) {               // use brute force search for small polygons
        int max = 0;
        for (int i=1; i<n; i++)     // for each point in {V1,...,Vn-1}
            if (above(U, V[i], V[max]))  // if V[i] is above prior V[max]
                 max = i;                // new max index = i
        return max;
    }

    // use binary search for large polygons
    int     a, b, c;            // indices for edge chain endpoints
    Vector  A, C;               // edge vectors at V[a] and V[c]
    int     upA, upC;           // test for "up" direction of A and C

    a=0; b=n;                   // start chain = [0,n] with V[n]=V[0]
    A = V[1] - V[0];
    upA = up(U,A);
    // test if V[0] is a local maximum
    if (!upA && !above(U, V[n-1], V[0]))    //  V[0] is the maximum
        return 0;

    for(;;) {
        c = (a + b) / 2;        // midpoint of [a,b], and 0<c<n
        C = V[c+1] - V[c];
        upC = up(U,C);
        if (!upC && !above(U, V[c-1], V[c])) // V[c] is a local maximum
            return c;                        // thus it is the maximum

        // no max yet, so continue with the  binary search
        // pick one of the two subchains [a,c]  or [c,b]
        if (upA) {                       // A points up
            if (!upC) {                      // C points down
                 b = c;                       // select [a,c]
            }
            else {                           // C points up
                 if (above(U, V[a], V[c])) {    // V[a] above V[c]
                     b = c;                       // select [a,c]
                 }
                 else {                         // V[a] below V[c]
                     a = c;                       // select [c,b]
                     A = C;
                     upA = upC;
                 }
            }
        }
        else {                           // A points down
            if (upC) {                       // C points up
                 a = c;                       // select [c,b]
                 A = C;
                 upA = upC;
            }
            else {                           // C points down
                 if (below(U, V[a], V[c])) {    // V[a] below V[c]
                     b = c;                       // select [a,c]
                 }
                 else {                         // V[a] above V[c]
                     a = c;                       // select [c,b]
                     A = C;
                     upA = upC;
                 }
            }
        }
        // have a new (reduced) chain [a,b]
        if (b <= a+1)           // the chain is impossibly small
            return (-1);        // return an error: something's wrong
    }
}
//===================================================================

 

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