前言
在計算幾何中,我們經常需要尋找一個2D多邊形(Polygon)的極值點。例如通過x\y的極值點,我們可以定義一個多邊形的包圍盒。更一般的情況下,我們可能會需要尋找一個多邊形在任意方向上的極值點。對於n個點的點集,很容易找到一個O(n)的算法,只需要依次測試每個點即可。然而對於凸多邊形,可以利用二分搜索的思想在時間內找到極值點。首先,定義一個任意的方向向量u,接下來我們詳細講解該算法的思路。
平面點集和簡單多邊形的凸包在這兩篇博文中已經介紹過了:平面點集的凸包算法、多邊形快速凸包算法。因此,對於平面點集和任意簡單多邊形只需要先計算凸包,然後再計算凸包極值點即可。其算法複雜度由凸包算法決定。
凸包極值點算法
首先,定義2D凸多邊形S由n個點構成V0,V1,...,Vn-1,Vn=V0,並且沿着逆時針方向給定。
定義爲第i條邊(從Vi到Vi+1),爲邊向量。
目標:找到S中的所有點在u方向上的極值點。也就是將點投影在u說定義的直線上,極值點就是投影點的極值點,如下圖所示:
可以看到,相對於u,Vi在Vj之上。也就是(Vi-Vj)與u之間形成一個銳角,相當於下面的幾何條件:
這個條件可以幫助我們判斷,一條邊相對於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
儘管算法是線性的,但還可以通過二分搜索加快。
二分搜索
對於一個凸多邊形的點集,我們可以通過二分搜索,在時間內找到極值點。
首先,假設極大值點在頂點Va和Vb之間,Vc爲Va和Vb的中間點,爲了實現二分搜索,我們需要將下一步的搜索範圍縮小到[a, c]或者[c, b]。由於多邊形是凸的,我們可以通過比較A、C處的邊向量、來做到這一點。下面詳細說明:
如上圖所示。假定u向上,A、B、C的相對位置可能有6種情況。對於每種情況,要麼[a, c],要麼[c, b]包含最大值點。這是因爲,多邊形S在u方向上是單調的,它由兩個單調鏈組成,從最小值點開始,到最大值點單調遞增,然後再單調遞減回到最小值點。而且無論u的方向如何,這個結論都是成立的。
接下來,我們就能構造二分搜索了。從開始,,在每一步,我們需要確定是將a增加到c,還是b減小到c,將包含最大值的區間縮小一半。當然,我們需要檢查,是否已經找到了一個局部極大值點Vc,這只需要檢查和的符號是否一致即可。如果兩者的符號不一致,說明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
}
}
//===================================================================