凸包算法知识总结
首先,什么是凸包?
假設(shè)平面上有p0~p12共13個(gè)點(diǎn),過某些點(diǎn)作一個(gè)多邊形,使這個(gè)多邊形能把所有點(diǎn)都“包”起來。當(dāng)這個(gè)多邊形是凸多邊形的時(shí)候,我們就叫它“凸包”。
處理何種問題:凸包可以看成在木板上釘許多釘子,用一根橡皮筋框住所有釘子所得到的多邊形,最終能求得都由哪些釘子構(gòu)成該凸包。如下圖所示:
然后,什么是凸包問題?
我們把這些點(diǎn)放在二維坐標(biāo)系里面,那么每個(gè)點(diǎn)都能用 (x,y) 來表示。
現(xiàn)給出點(diǎn)的數(shù)目13,和各個(gè)點(diǎn)的坐標(biāo)。求構(gòu)成凸包的點(diǎn)?
解一:窮舉法(蠻力法)
時(shí)間復(fù)雜度:O(n3)。
思路:兩點(diǎn)確定一條直線,如果剩余的其它點(diǎn)都在這條直線的同一側(cè),則這兩個(gè)點(diǎn)是凸包上的點(diǎn),否則就不是。
步驟:
將點(diǎn)集里面的所有點(diǎn)兩兩配對(duì),組成 n(n-1)/2 條直線。
對(duì)于每條直線,再檢查剩余的 (n-2) 個(gè)點(diǎn)是否在直線的同一側(cè)。
如何判斷一個(gè)點(diǎn) p3 是在直線 p1p2 的左邊還是右邊呢?(坐標(biāo):p1(x1,y1),p2(x2,y2),p3(x3,y3))
當(dāng)上式結(jié)果為正時(shí),p3在直線 p1p2 的左側(cè);當(dāng)結(jié)果為負(fù)時(shí),p3在直線 p1p2 的右邊。
解二:分治法
時(shí)間復(fù)雜度:O(n㏒n)。
思路:應(yīng)用分治法思想,把一個(gè)大問題分成幾個(gè)結(jié)構(gòu)相同的子問題,把子問題再分成幾個(gè)更小的子問題……。然后我們就能用遞歸的方法,分別求這些子問題的解。最后把每個(gè)子問題的解“組裝”成原來大問題的解。
步驟:
1.把所有的點(diǎn)都放在二維坐標(biāo)系里面。那么橫坐標(biāo)最小和最大的兩個(gè)點(diǎn) P1 和 Pn 一定是凸包上的點(diǎn)(為什么呢?用反證法很容易證明,這里不詳講)。直線 P1Pn 把點(diǎn)集分成了兩部分,即 X 軸上面和下面兩部分,分別叫做上包和下包。
2.對(duì)上包:求距離直線 P1Pn 最遠(yuǎn)的點(diǎn),即下圖中的點(diǎn) Pmax 。
3.作直線 P1Pmax 、PnPmax,把直線 P1Pmax 左側(cè)的點(diǎn)當(dāng)成是上包,把直線 PnPmax 右側(cè)的點(diǎn)也當(dāng)成是上包。
4.重復(fù)步驟 2、3。
對(duì)下包也作類似操作。
然而怎么求距離某直線最遠(yuǎn)的點(diǎn)呢?我們還是用到解一中的公式:
設(shè)有一個(gè)點(diǎn) P3 和直線 P1P2 。(坐標(biāo):p1(x1,y1),p2(x2,y2),p3(x3,y3))
對(duì)上式的結(jié)果取絕對(duì)值,絕對(duì)值越大,則距離直線越遠(yuǎn)。
注意:在步驟一,如果橫坐標(biāo)最小的點(diǎn)不止一個(gè),那么這幾個(gè)點(diǎn)都是凸包上的點(diǎn),此時(shí)上包和下包的劃分就有點(diǎn)不同了,需要注意。
解三:Jarvis步進(jìn)法
時(shí)間復(fù)雜度:O(nH)。(其中 n 是點(diǎn)的總個(gè)數(shù),H 是凸包上的點(diǎn)的個(gè)數(shù))
思路:
縱坐標(biāo)最小的那個(gè)點(diǎn)一定是凸包上的點(diǎn),例如圖上的 P0。
從 P0 開始,按逆時(shí)針的方向,逐個(gè)找凸包上的點(diǎn),每前進(jìn)一步找到一個(gè)點(diǎn),所以叫作步進(jìn)法。
怎么找下一個(gè)點(diǎn)呢?利用夾角。假設(shè)現(xiàn)在已經(jīng)找到 {P0,P1,P2} 了,要找下一個(gè)點(diǎn):剩下的點(diǎn)分別和 P2 組成向量,設(shè)這個(gè)向量與向量P1P2的夾角為 β 。當(dāng) β 最小時(shí)就是所要求的下一個(gè)點(diǎn)了,此處為 P3 。
注意:
找第二個(gè)點(diǎn) P1 時(shí),因?yàn)橐呀?jīng)找到的只有 P0 一個(gè)點(diǎn),所以向量只能和水平線作夾角 α,當(dāng) α 最小時(shí)求得第二個(gè)點(diǎn)。
共線情況:如果直線 P2P3 上還有一個(gè)點(diǎn) P4,即三個(gè)點(diǎn)共線,此時(shí)由向量P2P3 和向量P2P4 產(chǎn)生的兩個(gè) β 是相同的。我們應(yīng)該把 P3、P4 都當(dāng)做凸包上的點(diǎn),并且把距離 P2 最遠(yuǎn)的那個(gè)點(diǎn)(即圖中的P4)作為最后搜索到的點(diǎn),繼續(xù)找它的下一個(gè)連接點(diǎn)。
解四:Graham掃描法
時(shí)間復(fù)雜度:O(n㏒n)
思路:Graham掃描的思想和Jarris步進(jìn)法類似,也是先找到凸包上的一個(gè)點(diǎn),然后從那個(gè)點(diǎn)開始按逆時(shí)針方向逐個(gè)找凸包上的點(diǎn),但它不是利用夾角。
步驟:
把所有點(diǎn)放在二維坐標(biāo)系中,則縱坐標(biāo)最小的點(diǎn)一定是凸包上的點(diǎn),如圖中的P0。
把所有點(diǎn)的坐標(biāo)平移一下,使 P0 作為原點(diǎn),如上圖。
計(jì)算各個(gè)點(diǎn)相對(duì)于 P0 的幅角 α ,按從小到大的順序?qū)Ω鱾€(gè)點(diǎn)排序。當(dāng) α 相同時(shí),距離 P0 比較近的排在前面。例如上圖得到的結(jié)果為 P1,P2,P3,P4,P5,P6,P7,P8。我們由幾何知識(shí)可以知道,結(jié)果中第一個(gè)點(diǎn) P1 和最后一個(gè)點(diǎn) P8 一定是凸包上的點(diǎn)。
(以上是準(zhǔn)備步驟,以下開始求凸包)
以上,我們已經(jīng)知道了凸包上的第一個(gè)點(diǎn) P0 和第二個(gè)點(diǎn) P1,我們把它們放在棧里面。現(xiàn)在從步驟3求得的那個(gè)結(jié)果里,把 P1 后面的那個(gè)點(diǎn)拿出來做當(dāng)前點(diǎn),即 P2 。接下來開始找第三個(gè)點(diǎn):
連接 棧最上面的連個(gè)元素(即P0和棧頂)的那個(gè)點(diǎn),得到直線 L 。看當(dāng)前點(diǎn)是在直線 L 的右邊還是左邊。如果在直線的右邊就執(zhí)行步驟5;如果在直線上,或者在直線的左邊就執(zhí)行步驟6。
如果在右邊,則棧頂?shù)哪莻€(gè)元素不是凸包上的點(diǎn),把棧頂元素出棧。執(zhí)行步驟4。
當(dāng)前點(diǎn)是凸包上的點(diǎn),把它壓入棧,執(zhí)行步驟7。
檢查當(dāng)前的點(diǎn) P2 是不是步驟3那個(gè)結(jié)果的最后一個(gè)元素。是最后一個(gè)元素的話就結(jié)束。如果不是的話就把 P2 后面那個(gè)點(diǎn)做當(dāng)前點(diǎn),返回步驟4。
最后,棧中的元素就是凸包上的點(diǎn)了。
解五:Melkman算法
說真的,這個(gè)算法我也還沒有看清。網(wǎng)上的資料也少的可憐,我暫且把網(wǎng)上的解釋截個(gè)圖在這里,往后搞懂以后再回來補(bǔ)上。
性能:由一個(gè)快排(O(nlogn))和一個(gè)遍歷找點(diǎn)(O(n)),總體時(shí)間復(fù)雜度為O(nlogn)。
原理:
點(diǎn):A(x1,y1),B(x2,y2)
向量AB=(x2-x1,y2-y1)=(x,y)
向量的叉積:a X b =
通過結(jié)果的正負(fù)判斷兩矢量之間的順逆時(shí)針關(guān)系
l 若a X b > 0,表示a在b的順時(shí)針方向上
l 若a X b < 0,表示a在b的逆時(shí)針方向上
l 若a X b == 0,表示a與b共線,但不確定方向是否相同
例如:
A(0,0)
B(2,2)
C(3,1)
D(2,-1)
AB(2,2),AC(3,1),AD(2,-1)
AC X AB = 32-12 = 4>0
AC在AB的順時(shí)針方向上,即點(diǎn)C在向量AB的下面。
實(shí)現(xiàn)步驟:
排序:按照x由小到大排序,如果x相同,按照y由小到大排序。
排序之后第一個(gè)點(diǎn)必為凸包上的點(diǎn)(證明自己意淫一下,有x最大、x最小、y最小、y最大的點(diǎn)都必在凸包上)。
選最近兩個(gè)剛?cè)胪拱狞c(diǎn),再在排序中依次選點(diǎn),根據(jù)上面所提及到的原理,判斷該點(diǎn)在凸包那兩點(diǎn)的順時(shí)針還是逆時(shí)針方向。
如果在逆時(shí)針方向,將該點(diǎn)加入凸包,否則判定出之前進(jìn)入凸包的點(diǎn)不合格,刪除該凸包點(diǎn),重復(fù)第三步,直到該點(diǎn)加入凸包(也就是說每個(gè)點(diǎn)都曾進(jìn)過凸包,只是后來有些被刪了)。
以上就是下凸包的構(gòu)成步驟,上凸包參考下凸包,基本沒有什么差別,因?yàn)樵谂袛鄷r(shí)是判斷是否為逆時(shí)針,別誤以為是在判段該點(diǎn)在向量的下方,上凸包就不可用了,對(duì)于逆時(shí)針而言都是一樣的。
這種方法求出來的點(diǎn)是凸包沿著逆時(shí)針方向找出來的,首位相接且第一個(gè)點(diǎn)重復(fù)兩次,所以除了點(diǎn)只有一個(gè)的情況下,記得點(diǎn)的個(gè)數(shù)減一。
備注:對(duì)于題目要求求凸包構(gòu)成的面積時(shí),可以參考以下圖示求法:
輸入樣例解釋:
11—散點(diǎn)樣例個(gè)數(shù)
5 8 —散點(diǎn)坐標(biāo)
12 56
5 2
125 1
15 66
45 77
55 6
45 2
232 5
45 12
54 66
輸出樣例解釋:
tot=7 —構(gòu)成凸包點(diǎn)的個(gè)數(shù)
1: 5.00 , 2.00 —沿著凸包逆時(shí)針方向,且保留兩位小數(shù)
2: 125.00 , 1.00
3: 232.00 , 5.00
4: 45.00 , 77.00
5: 15.00 , 66.00
6: 12.00 , 56.00
7: 5.00 , 8.00
//求凸包,時(shí)間復(fù)雜度nlogn #include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> using namespace std;const int MaxN=10010;int n,tot;//n為點(diǎn)的個(gè)數(shù),tot為凸點(diǎn)的個(gè)數(shù) struct point {double x,y; }; point p[MaxN],CHP[MaxN];//CHP為凸包最后所構(gòu)成的點(diǎn)bool cmp(point a,point b)//水平排序,按x從大到小排,如果x相同,按y從大到小排序 {return (a.x<b.x||(a.x==b.x&&a.y<b.y)); }double xmul(point a,point b,point c)//叉積 {return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x); }void Andrew() {sort(p,p+n,cmp);tot=0;for(int i=0; i<n; ++i) //計(jì)算下半個(gè)凸包{while(tot>1&&xmul(CHP[tot-2],CHP[tot-1],p[i])<0)--tot;CHP[tot++]=p[i];}int k=tot;for(int i=n-2; i>=0; --i) //計(jì)算上半個(gè)凸包{while(tot>k&&xmul(CHP[tot-2],CHP[tot-1],p[i])<0)--tot;CHP[tot++]=p[i];}if(n>1)//對(duì)于只有一個(gè)點(diǎn)的包再單獨(dú)判斷--tot; }int main() {scanf("%d",&n);for(int i=0; i<n; ++i){scanf("%lf%lf",&p[i].x,&p[i].y);}Andrew();printf("tot=%d\n",tot);for(int i=0; i<tot; ++i){printf("%d: %.2lf , %.2lf\n",i+1,CHP[i].x,CHP[i].y);}return 0; }一些預(yù)備知識(shí)點(diǎn):
首先在二維坐標(biāo)下介紹一些定義:
點(diǎn):A(x1,y1),B(x2,y2)
向量:向量AB=( x2 - x1 , y2 - y1 )= ( x , y );
向量的模 |AB| = sqrt ( xx+yy );
向量的點(diǎn)積: 結(jié)果為 x1x2 + y1y2。
點(diǎn)積的結(jié)果是一個(gè)數(shù)值。
點(diǎn)積的集合意義:我們以向量 a 向向量 b 做垂線,則 | a | * cos(a,b)為 a 在向量 b 上的投影,即點(diǎn)積是一個(gè)向量在另一個(gè)向量上的投影乘以另一個(gè)向量。且滿足交換律
應(yīng)用:可以根據(jù)集合意義求兩向量的夾角,
cos(a,b) =( 向量a * 向量b ) / (| a | * | b |) = (x1x2 + y1y2) / (| a | * | b |)
向量的叉積: 結(jié)果為 x1y2-x2y1
叉積的結(jié)果也是一個(gè)向量,是垂直于向量a,b所形成的平面,如果看成三維坐標(biāo)的話是在 z 軸上,上面結(jié)果是它的模。
方向判定:右手定則,(右手半握,大拇指垂直向上,四指右向量a握向b,大拇指的方向就是叉積的方向)
叉積的集合意義:1:其結(jié)果是a和b為相鄰邊形成平行四邊形的面積。
2:結(jié)果有正有負(fù),有sin(a,b)可知和其夾角有關(guān),夾角大于180°為負(fù)值。
3:叉積不滿足交換律
應(yīng)用:
1:通過結(jié)果的正負(fù)判斷兩矢量之間的順逆時(shí)針關(guān)系
若 a x b > 0表示a在b的順時(shí)針方向上
若 a x b < 0表示a在b的逆時(shí)針方向上
若 a x b == 0表示a在b共線,但不確定方向是否相同
總結(jié)
- 上一篇: Pipe HDU - 2150(判断线段
- 下一篇: Power Network POJ -