凸包小結 數學:凸包算法詳解

先說部分資料來源(蒟蒻也是從他們那裏學會的):html

數學:凸包算法詳解——愛國吶node

計算幾何之凸包(convexHull)----Graham掃描法——天澤28ios

話說原本在學斜率優化DP,結果由於某位坑爹博主的一句原本沒有問題的話:算法

是否是很像一個下凸包? 
咱們用當前的斜率k從下方去不斷逼近下凸包,最終會先碰到哪個點? post

我莫名其妙的去學了凸包,以爲學完以後斜率DP說不定能好作點,可是。。。。。。如今依然看不明白。優化

那麼進入正題,先看這樣一個例題:url

mhr:明顯,這個題咱們能夠暴力枚舉!spa

博主:滾!.net

能夠發現,暴力是絕對不可取的,其實若是這個圖就擺在你面前的話,你是很容易就能看出來,柵欄長度就是最外面的一圈點,可是電腦並無眼睛,因此咱們就要使用一些算法來計算出來那外面的一圈,也就是所謂的凸包。有一個比較學術的說法,來自某度某科:3d

凸包(Convex Hull)是一個計算幾何(圖形學)中的概念。
在一個實數向量空間V中,對於給定集合X,全部包含X的凸集的交集S被稱爲X的凸包。X的凸包能夠用X內全部點(X1,...Xn)的凸組合來構造.
在二維歐幾里得空間中,凸包可想象爲一條恰好包著全部點的橡皮圈。
用不嚴謹的話來說,給定二維平面上的點集,凸包就是將最外層的點鏈接起來構成的凸多邊型,它能包含點集中全部的點。

咱們有不少不一樣的方法求出凸包,不過這裏介紹的是性價比很高的Graham算法。

graham算法的整個操做基本都在一個棧中完成。若是設全部點的集合爲點集Q,那麼Q中的全部點都要入棧一次,而後再把一部分不符合要求的點彈出棧,最後剩在棧中的點就是凸包上的點了。

具體實現步驟以下

  1. 首先咱們要選取一個基點o,要求在點集Q中,基點o縱座標必須是最小的,若是有相同最小的縱座標,那麼選取橫座標最小的,若是還有相同的。。確定是重點,不用管它就是了。如何快速找到?若是你不嫌麻煩,大能夠sort排序以後選第一個,然而實際上,咱們只須要找出那個最下面同時是最左邊的點就能夠了,由於以後整個點集還會從新排序,因此這一開始的順序沒什麼用。
  2. 而後咱們把剩下的全部點以o爲極點,進行極角排序,角小的放在前面,若是角度相同,那麼按照到點o的距離排序。

  3. 設全部的點事p1,p2.....pn。將o,p1,p2三個點壓入棧,開始遍歷全部剩下的點。

  4. 對每個新遍歷到的點,很明顯咱們須要逆時針旋轉當前方向,若是有一個點順時針旋轉了,那麼咱們就把棧頂的點彈出,直到符合逆時針旋轉這個要求爲止。

看上去十分簡明扼要易懂是否是???

讀者:打死這個博主,寫這些東西我能看懂啥??那個順時針逆時針我能看出來,電腦那個愚蠢的東西咋看出來??

判斷順時針仍是逆時針旋轉,咱們要用到一個東西——叉積。

好吧又一個新名詞。某度某科這麼定義叉積:

向量積,數學中又稱外積、叉積,物理中稱矢積、叉乘,是一種在向量空間中向量的二元運算。與點積不一樣,它的運算結果是一個向量而不是一個標量。而且兩個向量的叉積與這兩個向量和垂直。其應用也十分普遍,一般應用於物理學光學和計算機圖形學中。

嗯。。。這其實什麼也沒說明白,這麼說吧,貌似博主們和筆者都是一致認爲:叉積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);
}

其實也不是很詳細了。。。不過筆者自認爲碼風清晰易懂(逃~~

相關文章
相關標籤/搜索