凸包複習php
幾何專題刷了有大半年了,忽然發現之前學的居然忘的差很少了,下午又花了點時間複習一下,感受挺簡單的(全是靠模板。。node
資料上沒有適合本身的模板,因而複習一下本身整理一下模板。算法
先來接觸點預備函數:函數
1、 點的定義:url
int n,tot;//n爲二維平面上點的個數,tot爲凸包上點的個數 struct node { int x,y; }a[N],p[N];//p[]用來儲存凸包
2、距離公式:spa
double dis(node a,node b) { return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y); }3、 叉積:返回結果爲正說明p2在向量p0p1的左邊(三點構成逆時針方向);爲負則相反;爲0則三點共線(叉積的性質很重要)
double multi(node p0,node p1,node p2) { return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y); }4、 極角排序: 極角排序是根據座標系內每個點與x軸所成的角,逆時針比較,。按照角度從小到大的方式排序。
int cmp(node p1,node p2)//極角排序; { int x=multi(p1,p2,a[0]); if(x>0||(x==0&&dis(p1,a[0])<dis(p2,a[0]))) return 1; return 0; }graham 算法:O(nlogn)
void Graham() { int k=0; for(int i=0;i<n;i++) if(a[i].y<a[k].y||(a[i].y==a[k].y&&a[i].x<a[k].x)) k=i; swap(a[0],a[k]); sort(a+1,a+n,cmp); tot=2,p[0]=a[0],p[1]=a[1]; for(int i=2;i<n;i++) { while(tot>1&&multi(p[tot-1],p[tot-2],a[i])>=0) tot--; p[tot++]=a[i]; } }
以上鍊接起來就是求凸包的模板。.net
光有代碼不行,還得懂原理code
附一篇較好的博客:傳送門blog
看懂了那篇博客基本上凸包就已經會了。排序
先用一個經典問題來引入吧: 在一片有限區域的草坪上有n個木樁(n>=3),如今要求用一根繩子將這些木樁圍起來,求所需繩子的最短周長。
以上問題中繩子所圍成的圖形就是一個凸包。要求周長,那麼必須先要求出繩子接觸了哪些木樁。
求凸包有若干種方法,這裏只介紹Graham算法。
大體思路:先肯定凸包上一個點,再用這個點做爲參照將剩餘的點進行極角排序,根據性質能夠獲得凸包上的第二個點,再用已知的凸包上的點利用叉積的性質進行肯定下一個點,直到圍成一個凸包。
① 先肯定凸包上的一個點,咱們知道橫縱座標最大或最小的點確定在凸包上,咱們就選取縱座標最小的點做爲第一個點,若是有多個縱座標最小的點怎麼辦呢,咱們選取橫座標最小的點。這是爲極角排序作準備。
② 極角排序:上面提到了極角排序的定義,咱們能夠以選取的第一個點p0做爲原點(參照),其餘點與p0點的連線與x軸的夾角進行排序,若是夾角相同怎麼辦呢,按與p0的距離排序,這一步考驗對叉積的性質利用。
③ 除了p0,排序後的第一個點p1和最後一個點必定是凸包上的點,想一想爲何。這樣咱們就獲得了p0,p1,咱們把它們放入棧裏,如今用p0,p1來肯定p2(棧頂的兩個點來肯定下一個點),仍是用叉積的性質,棧頂兩個點連成線(向量),看當前點是在直線的左邊仍是右邊,若是在右邊,說明棧頂的那個點不是凸包上的點,退棧便可,而後重複判斷棧頂兩個元素與當前元素的關係;反之,則說明當前點是凸包上的點。
④ 將當前點入棧,若是當前點不是凸包上的點,後面的點天然會將這個點gank。對下一個點進行相同的操做。最後棧中的點就是凸包上的點啦。
詳細請參考上面代碼。
推薦幾道例題吧:
NYOJ-78 圈水池 入門凸包輸出點
POJ-2007 Scrambled Polygon 求凸包而且按原點爲第一點的逆時針方向輸出
HDU-1392 Surround the Trees 入門求凸包周長
POJ-3348 Cows 入門求面積
POJ-2187 Beauty Contest 旋轉卡殼求平面最遠點對
POJ-1228 Grandpa's Estate 穩定凸包
POJ-1696 Space Ant 凸包應用(德黑蘭賽區好題 )
POJ-1113 Wall 凸包簡單應用(推薦)