凸包小結


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

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

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

話說本來在學斜率優化DP,結果因為某位坑爹博主的一句本來沒有問題的話:

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

我莫名其妙的去學了凸包,覺得學完之后斜率DP說不定能好做點,但是。。。。。。現在依然看不明白。

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

mhr:明顯,這個題我們可以暴力枚舉!

博主:滾!

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

凸包(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);
}

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM