計算幾何入門 1.4:凸包的構造——Jarvis March算法

回顧凸包構造算法:極點法、極邊法和增量構造法,其複雜度分別爲O(n^4)、O(n^3)和O(n^2),效率經過優化已經大大提高了。接下來引入一種新的算法——Jarvis March,其複雜度也是O(n^2),但是相較於增量構造在最好情況下效率是較高的。

一、選擇排序

依舊是先回顧一個經典算法:選擇排序(selection sort)。排序過程可以歸結爲下圖:

對比插入排序,可以發現sorted部分變成了從後向前擴張。算法的整體思路依然是增量式的,每次選取unsorted部分中的最大元素,和sorted部分前一個元素交換,重複上述過程完成sorted部分的擴張最終使得序列有序。

 

和插入排序每次隨便從unsorted部分選取元素不同,選擇排序每次選取出的unsorted最大元素放在sorted前一個位置上,也就意味着整個unsorted部分必然不會超過sorted部分。從算法整體框架考慮,每次我們都是維護一個局部解(也就是sorted部分),然後從尚未處理的部分(也就是unsorted部分)找到一個與當前局部解“緊密相關的元素”(相當於選取的最大元素)。這個思想爲解決凸包問題帶來了新思路。

 

二、策略

再來考慮爲何極邊法複雜度高達O(n^3)。實際上我們要對點集中所有邊進行遍歷,這需要n^2複雜度,然後對每個邊進行鑑別,又需要n複雜度,因此總體複雜度高達O(n^3)。那麼該如何改進呢?這就可以運用選擇排序的思想:將下一個要查找的邊縮小到一個小範圍,而非遍歷所有邊。

 

先對算法的大致過程有直觀的認識(標識爲:已找到極邊數/所有極邊數):

 

首先從任何一個極點(後面說明如何找到這個點)開始(圖中0/5),然後找到一條以這個極點爲端點的極邊(1/5)。接着沿着極邊另一個端點(endpoint)出發,再找出下一條極邊(2/5)。如此反覆操作,最終會找到一條以最初極點爲endpoint的極邊,得到一個封閉的環,凸包也構造完成。凸包構造過程類似於選擇排序中sorted不斷向前擴展一樣,不斷擴展局部解,最後得到問題最終解。

 

凸包構造的問題由此分解爲一個個子問題:如何從endpoint出發找到下一條極邊。

 

 

三、繼續to left test

考慮以下一般情況:

我們從極點o開始尋找極邊,假設當前找到的極邊是ik,接下來要做的工作是找到從k出發的另一條極邊ks,即找到極點s。

 

顯然,s來自於其他那些尚未處理的點中,那麼s與其他點相比有什麼特徵?觀察發現,ik作爲一條極邊,它的右側肯定都是空的,所有其他點都在ik左側。畫出k與其他候選點的有向直線,例如下圖中的ks,kt:

注意圖中紅色標出的角度,可以看出ks與ik的夾角比kt小,也就是ks比kt相較於ik偏左的角度更小。實際上ks偏左的角度比其他任何從k出發的邊都小,這就是s點的判定依據。

 

這樣就找到了從其餘點中選擇s點的思路:任選兩個點,從k出發過這兩點做有向邊,看哪個偏左的角度更小就留下,另一點丟棄。然後再拿一點與留下的點比較,反覆這個過程,最終留下的就是要找的s點。

 

問題至此轉化爲:如何比較兩條有向邊(例如ks和kt)相較於另一有向直線(例如ik)誰偏左的角度更小。

 

當然可以通過計算三角函數的方法來比較,這是最直觀的數學思維。但是這樣計算十分複雜,更重要的是引入了誤差。這時候又要使用to left test這個基礎方法來解決問題了。

具體做法就是以在ks和kr中以任意個爲基準(如以ks爲基準),對另一點(如t)做to left test。上圖點t和有向邊ks的to left test結果爲true,t在ks左邊,因此ks偏左的角度更小,捨棄點t。

 

類比選擇排序來理解,sorted部分就相當於已得到的極邊(從極點o開始到ik的首尾相連的極邊),unsorted部分相當於其餘點,從unsorted部分取出極大值就相當於找到點s——能構成最小偏角的點。選擇排序中的選擇過程需要比較元素大小,就要由一種比較器完成,而上述比較偏角的過程也可以抽象爲一種比較器的操作。構造凸包的算法框架與選擇排序相同,只是比較器替換爲to left test而已。

 

//此處只是考慮一般情況,一些特殊細節未進行處理。例如在st上有s和s'兩點,這兩點的取捨問題未考慮。當然爲了理解算法整體框架忽略特殊情況是很必要的。

 

然後考慮一個細節問題,也就是上文提到的算法最開始第一個極點如何確定。任何一個極點都可以使用,我們沒必要去計算出哪個點是極點。可以選取y座標最小的點,也就是最低點,在沒有退化的情況下,這個點一定是一個極點。如果情況退化,有多個最低點(如例圖中所示),我們就去選x座標最小的那個點,也就是最左邊的點即可。這種方法選出的點稱爲lowest-then-leftmost point(LTL)。注意選取的規則的先後順序,先選lowest,若點不唯一再選leftmost。

四、Jarvis March

類比選擇排序的過程,我們得到的凸包構造算法就是Jarvis March算法,又稱gift wrapping算法(算法過程如包裝禮物一樣)。接下來看算法具體實現方法。

bool ToLeft(Point p, Point q, Point s)
{
	int area2 =   p.x * q.y - p.y * q.x 
				+ q.x * s.y - q.y * s.x
				+ s.x * p.y - s.y * p.x;
	
	return area2 > 0;	//左側爲真
}

int ltl(Point S[], int n)
{
	int LTL = 0;
	for (int k = 1; k < n; k++)
		if (S[k].y < S[LTL].y ||
			(S[k].y == S[LTL].y &&
			 S[k].x < S[LTL].x))
			LTL = K;
	return LTL;
}

void Jarvis(Point S[], int n)
{
	for (int = 0; k < n; k++)
		S[k].extreme = false;	//首先將所有點標記爲非極點
	
	int LTL = ltl(s, n);	//找到LTL
	int k = LTL;			//將LTL作爲第一個極點
	
	do
	{
		S[k].extreme = true;	//判定爲極點
		int s = -1;
		
		//選取下一個極點,每次比較兩個點s和t,
		//做點t和有向邊ks的to left test,最終找到s
		for (int t = 0; t < n; t++)
			if (t != k && t != s &&	//除了k和s外每個點
				(s == -1 || !ToLeft(P[k], P[s], P[t]))) // 
				s = t;	//如果t在s右邊
		
		S[k].succ = s;	//k點的後繼爲s
		k = s;	//s變爲下一次查找的起點
	} while (LTL != k)
}


最後分析Jarvis March算法相較於增量構造法的優勢。二者都是O(n^2)的複雜度,Jarvis March算法的優勢在於其的“輸出敏感性(output sensitive)”。考慮點集S,共有n個點,來構造S上的凸包。

 

何爲“輸出敏感性”?Jarvis March算法每次新加入一條邊都會耗費n的複雜度,但是構造過程一共會加入的邊數往往比n少。如下圖(設n = 7):

在非退化爲共線的前提下,最好情況爲只加入3條邊(複雜度爲O(3n)),最壞情況爲所有點都是極點,加入n-1條邊(複雜度爲O(n^2))。實際情況中最壞情況出現的機率很小,我們引入一個指標h來衡量凸包的極邊數(the size of convex hull):

h = |CH(S)|

Jarvis March算法算法的複雜度更準確的表示爲O(nh)。h又由最終輸出結果,即凸包本身來決定,輸出結果決定了構造過程的複雜度,這就是所謂的“輸出敏感性”。這種類型的算法又被稱爲output sensitive algorithm。這種特性在其它凸包算法中也會體現。

 

本文是學堂在線課程《計算幾何》的筆記,幫助理解和記錄思考過程,不夠嚴謹請見諒。

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