先說部分資料來源(蒟蒻也是從他們那裏學會的):html
數學:凸包算法詳解——愛國吶node
計算幾何之凸包(convexHull)----Graham掃描法——天澤28ios
話說原本在學斜率優化DP,結果由於某位坑爹博主的一句原本沒有問題的話:算法
是否是很像一個下凸包?
咱們用當前的斜率k從下方去不斷逼近下凸包,最終會先碰到哪個點? post
我莫名其妙的去學了凸包,以爲學完以後斜率DP說不定能好作點,可是。。。。。。如今依然看不明白。優化
那麼進入正題,先看這樣一個例題:url
mhr:明顯,這個題咱們能夠暴力枚舉!spa
博主:滾!.net
能夠發現,暴力是絕對不可取的,其實若是這個圖就擺在你面前的話,你是很容易就能看出來,柵欄長度就是最外面的一圈點,可是電腦並無眼睛,因此咱們就要使用一些算法來計算出來那外面的一圈,也就是所謂的凸包。有一個比較學術的說法,來自某度某科:3d
咱們有不少不一樣的方法求出凸包,不過這裏介紹的是性價比很高的Graham算法。
graham算法的整個操做基本都在一個棧中完成。若是設全部點的集合爲點集Q,那麼Q中的全部點都要入棧一次,而後再把一部分不符合要求的點彈出棧,最後剩在棧中的點就是凸包上的點了。
具體實現步驟以下
而後咱們把剩下的全部點以o爲極點,進行極角排序,角小的放在前面,若是角度相同,那麼按照到點o的距離排序。
設全部的點事p1,p2.....pn。將o,p1,p2三個點壓入棧,開始遍歷全部剩下的點。
對每個新遍歷到的點,很明顯咱們須要逆時針旋轉當前方向,若是有一個點順時針旋轉了,那麼咱們就把棧頂的點彈出,直到符合逆時針旋轉這個要求爲止。
看上去十分簡明扼要易懂是否是???
讀者:打死這個博主,寫這些東西我能看懂啥??那個順時針逆時針我能看出來,電腦那個愚蠢的東西咋看出來??
判斷順時針仍是逆時針旋轉,咱們要用到一個東西——叉積。
好吧又一個新名詞。某度某科這麼定義叉積:
向量積,數學中又稱外積、叉積,物理中稱矢積、叉乘,是一種在向量空間中向量的二元運算。與點積不一樣,它的運算結果是一個向量而不是一個標量。而且兩個向量的叉積與這兩個向量和垂直。其應用也十分普遍,一般應用於物理學光學和計算機圖形學中。
嗯。。。這其實什麼也沒說明白,這麼說吧,貌似博主們和筆者都是一致認爲:叉積a×b是點0,a,b和a+b組成的平行四邊形的向量面積(也就是有方向的面積)。若是計算出來的叉積是正,那麼a在b右側,不然a在b左側。若是增添一個公共端點c,那麼計算方法就是:(c-a)×(c-b)。P.s可能說的不是很明白,由於筆者對於插入數學公式這種操做仍是略不熟練,文章最上方的dalao的博客裏有比較清晰地證實。
那麼給出算法導論裏的證實,仍是比較明白的:
然而其實也有瑕疵,就是這個正負和你計算的順序是有關係的,常常有不等號寫反結果一直爆0的現象發生。因此有句老話「盡信書則不如無書」,不要過度相信書上說的,多實踐纔是真理。
其實應該手繪靜態步驟圖的,可是確實是沒那個實力,博主從小學開始美術就連B都沒得過,全是CD。。。。
那麼借來一張動態的給你們看一眼吧:
。
那麼基本上大致的就說完了,接下來就是代碼了。與往常不一樣,博主此次會加詳細的註釋誒:
p.s:以上面那道題目爲例。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> #define ll long long #define inf 50000000 #define re register #define MAXN 50005 using namespace std; struct node{ double x,y; }; node a[MAXN],stackk[MAXN]; double xx,yy; int n,top; inline int read() { int x=0,c=1; char ch=' '; while((ch>'9'||ch<'0')&&ch!='-')ch=getchar(); while(ch=='-') c*=-1,ch=getchar(); while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar(); return x*c; } inline double js(node a,node b)//計算距離自沒必要說 { return sqrt((a.x-b.x)*(a.x-b.x)*1.0+(a.y-b.y)*(a.y-b.y)*1.0); } inline bool cmp(node a,node b)//第一遍排序,來求基點。 { if(a.y==b.y) return a.x<b.x; return a.y<b.y; } inline double cross(node a,node b,node c)//計算以a爲公共端點,b與c的叉積。 { return (b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y); } inline bool cmp1(node a,node b)//極角排序 { double k=cross(stackk[1],a,b); if(k>0) return 1;//若是b在a時針方向返回1 else if(k==0) return js(stackk[1],a)<=js(stackk[1],b);//若是極角相等,則比較距離 else return 0; } int main() { n=read(); for(re int i=1;i<=n;i++){ scanf("%lf%lf",&a[i].x,&a[i].y); } if(n==1) {printf("0");return 0;} if(n==2) {printf("%.2lf",js(a[1],a[2]));return 0;} sort(a+1,a+n+1,cmp); stackk[++top]=a[1]; xx=stackk[top].x; yy=stackk[top].y; sort(a+2,a+n+1,cmp1); stackk[++top]=a[2]; stackk[++top]=a[3];//把p1,p2,p3壓入棧中。 for(re int i=4;i<=n;i++){ while(top>0&&cross(stackk[top-1],stackk[top],a[i])<0)//若是右旋轉了,就彈出棧頂的點 top--; stackk[++top]=a[i];//加入新點 } double ans=0; for(re int i=2;i<=top;i++)//點之間兩兩求距離。 ans+=js(stackk[i-1],stackk[i]); ans+=js(stackk[top],stackk[1]); printf("%.2lf",ans); }
其實也不是很詳細了。。。不過筆者自認爲碼風清晰易懂(逃~~