Graham Scan凸包算法

得到凸包的算法能夠算是計算幾何中最基礎的算法之一了。尋找凸包的算法有不少種,Graham Scan算法是一種十分簡單高效的二維凸包算法,可以在O(nlogn)的時間內找到凸包。算法

首先介紹一下二維向量的叉積(這裏和真正的叉積仍是不一樣的):對於二維向量a=(x1,y2)和b=(x2,y2),a×b定義爲x1*y2-y1*x2。而它的幾何意義就是|a||b|sin<a,b>。若是ab夾角小於180度(逆時針),那麼這個值就是正值,大於180度就是負值。須要注意的是,左乘和右乘是不一樣的。如圖所示:二維向量的叉積segmentfault

Graham Scan算法的作法是先定下一個起點,通常是最左邊的點和最右邊的點,而後一個個點掃過去,若是新加入的點和以前已經找到的點所構成的「殼」凸性沒有變化,就繼續掃,不然就把已經找到的最後一個點刪去,再比較凸性,直到凸性不發生變化。分別掃描上下兩個「殼」,合併在一塊兒,凸包就找到了。這麼說很抽象,咱們看圖來解釋:數組

咱們找下「殼」,上下實際上是同樣的。首先加入兩個點A和C:
請輸入圖片描述
而後插入第三個點G,並計算AC×CG的叉積,卻發現叉積小於0,也就是說逆時針方向上∠ACG大於180度,因而刪去C點,加入G點:
請輸入圖片描述請輸入圖片描述
而後就是依照這個步驟便能加入D點。在AD上方是以D爲起點。就可以找到AGD和DFEA兩個凸殼。合併就獲得了凸包。
請輸入圖片描述
關於掃描的順序,有座標序和極角序兩種。座標序是比較兩個點的x座標,若是小的先被掃描(掃描上凸殼的時候反過來);若是兩個點x座標相同,那麼就比較y座標,小的先被掃描(掃描上凸殼的時候也是反過來)。極角序使用arctan2函數的返回值進行比較,我沒寫過因此也不是很清楚。
程序能夠寫得很精簡,如下是我用C++寫得凸包程序函數

/*
d[]是一個Point的數組,Point有兩個兩個屬性x和y,同時支持減法操做和det(叉積)。
convex數組保存被選中的凸包的點的編號,cTotal是凸包中點的個數
*/
bool cmpPoint(const Point &a, const Point &b)  //比較座標序所用的比較函數
{
    if (a.x!=b.x) return a.x<b.x;
    return a.y<b.y;
}
void get_convex_hull()
{
    sort(d,d+N,cmpPoint);
    int Total=0,tmp;
    for (int i=0;i<N;++i)  //掃描下凸殼
    {
        while ( (Total>1) && 
                ((d[convex[Total-1]]-d[convex[Total-2]]).det(    //得到凸包中最後兩個點的向量
                d[i]-d[convex[Total-1]])<=0) ) Total--;                //得到準備插入的點和凸包中最後一點的向量,計算叉積
        convex[Total++]=i;
    }
    tmp=Total;
    for (int i=N-2;i>=0;--i)   //掃描上凸殼
    {
        while ( (Total>tmp) &&
                ((d[convex[Total-1]]-d[convex[Total-2]]).det(
                d[i]-d[convex[Total-1]])<=0) ) Total--;
        convex[Total++]=i;
    }
    cTotal=Total;
}

咱們來看一道題:POJ1113 Wall,題意是給一些點,找一個閉合曲線C,使C能包住全部的點,而且給定的點到C的距離最小爲L,問C的周長。稍微畫一畫就知道這個C的周長是這些點所構成的凸包的周長加上以L爲半徑的圓的周長。因而求一個凸包再加上2πL就能夠了。個人程序以下:spa

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>
using std::sort;
#define MAXN 1002
int N,L;
double  sqr(double a)
{
    return a*a; 
}
struct Point
{
    double x,y;
    inline Point operator- (const Point &t)
    {
        Point ret;
        ret.x=x-t.x;
        ret.y=y-t.y;
        return ret;
    }
    inline Point operator+ (const Point &t)
    {
        Point ret;
        ret.x=x+t.x;
        ret.y=y+t.y;
        return ret;
    }
    inline int det(const Point &t)
    {
        return x*t.y-t.x*y;
    }
    inline double dist(Point &t)
    {
        return sqrt(sqr(x-t.x)+sqr(y-t.y));
    }
}d[MAXN];
bool cmpPoint(const Point &a, const Point &b)
{
    if (a.x!=b.x) return a.x<b.x;
    return a.y<b.y;
}
int convex[MAXN],cTotal;
void get_convex_hull()
{
    sort(d,d+N,cmpPoint);
    int Total=0,tmp;
    for (int i=0;i<N;++i)
    {
        while ( (Total>1) && 
                ((d[convex[Total-1]]-d[convex[Total-2]]).det(
                d[i]-d[convex[Total-1]])<=0) ) Total--;
        convex[Total++]=i;
    }
    tmp=Total;
    for (int i=N-2;i>=0;--i)
    {
        while ( (Total>tmp) &&
                ((d[convex[Total-1]]-d[convex[Total-2]]).det(
                d[i]-d[convex[Total-1]])<=0) ) Total--;
        convex[Total++]=i;
    }
    cTotal=Total;
}
int main()
{
    scanf("%d%d",&N,&L);
    for (int i=0;i<N;++i)
    {
        scanf("%lf%lf",&d[i].x,&d[i].y);
    }
    get_convex_hull();
    double Ans=0;
    for (int i=0;i<cTotal-1;++i)
    {
        Ans+=d[convex[i]].dist(d[convex[i+1]]);
    }
    Ans+=d[convex[0]].dist(d[convex[cTotal-1]]);
    Ans+=3.1415926*2*L;
    printf("%.0lf\n",Ans);
    return 0;
}
相關文章
相關標籤/搜索