1. 程式人生 > >凸包-Andrew演算法&&Graham掃描法

凸包-Andrew演算法&&Graham掃描法

凸包簡介:

在二維平面上(二維凸包)給出若干個點,能夠包含這若干個點的面積最小的凸多邊形稱為凸包(可以想像有很多個釘子釘在牆上,然後用一個橡皮圈套在所有的釘子上,最後橡皮圈形成的就是一個凸包)。

Graham掃描法:

Graham掃描法是一種基於極角排序的進行求解的演算法,其大致流程如下:

①找一個一定在凸包上的點P0(一般找縱座標最小的點);

②將其餘所有的點以P0為基準進行極角排序;

③從P0出發掃描所有的點,不斷地更新最外圍的點,是否在最外圍可由叉乘判斷。這裡用個圖說明一下:

當前對點P進行判斷,P1,P2為前面加入的兩個點:

Ⅰ)若點P在內部(圖一),則有向量b叉乘向量a小於零,此時點P是凸包的頂點,應將P點加入凸包。

Ⅱ)若在點P在外部,則有向量b叉乘向量a大於零,此時應該將點P1捨棄,繼續對前兩個點進行判斷,直到P、P1、P2三個點滿足向量b叉乘向量a小於零,再將P點加入凸包。

當然網上面還有更詳細的例子,不懂得話可以再看看其餘的資料( ̄▽ ̄)。

具體程式碼如下:

//Graham掃描法-hdu1392
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=1e6+10;
const double esp=1e-10;
struct Point{
    double x,y;
    Point(){}
    Point(double _x,double _y){
        x=_x;y=_y;
    }
}P[maxn],Convexhull[maxn];
typedef Point Vector;
Vector operator + (Vector A,Vector B){
    return Vector(A.x+B.x,A.y+B.y);
}
Vector operator - (Vector A,Vector B){
    return Vector(A.x-B.x,A.y-B.y);
}
Vector operator * (Vector A,double d){
    return Vector(A.x*d,A.y*d);
}
Vector operator / (Vector A,double d){
    return Vector(A.x/d,A.y/d);
}
double Dot(Vector A,Vector B){
    return A.x*B.x+A.y*B.y;
}
double Cross(Vector A,Vector B){
    return A.x*B.y-A.y*B.x;
}
double Length(Vector A){
    return sqrt(Dot(A,A));
}
int dcmp(double x){
    return fabs(x)<esp?0:x<0?-1:1;
}
bool Angle_Cmp(Point p1,Point p2){
    double res=Cross(p1-P[0],p2-P[0]);
    return dcmp(res)>0||(dcmp(res)==0&&dcmp(Length(p1-P[0])-Length(p2-P[0]))<0);
}
int Graham(int n){
    int k=0;
    Point p0=P[0];
    for(int i=1;i<n;i++){
        if(p0.y>P[i].y||(p0.y==P[i].y&&p0.x>P[i].x)){
            k=i;p0=P[i];
        }
    }
    swap(P[0],P[k]);
    sort(P+1,P+n,Angle_Cmp);
    Convexhull[0]=P[0];                 //Assume n>2
    Convexhull[1]=P[1];
    int top=2;
    for(int i=2;i<n;i++){
        while(top>1&&Cross(P[i]-Convexhull[top-2],Convexhull[top-1]-Convexhull[top-2])>=0)top--;
        Convexhull[top++]=P[i];
    }
    return top;
}
int main(){
    freopen("in.txt","r",stdin);
    int n;
    while(~scanf("%d",&n)&&n){
        for(int i=0;i<n;i++)scanf("%lf%lf",&P[i].x,&P[i].y);
        int cnt=Graham(n);
        double ans=0;
        if(cnt!=2)ans+=Length(Convexhull[0]-Convexhull[cnt-1]);
        for(int i=0;i<cnt-1;i++)ans+=Length(Convexhull[i]-Convexhull[i+1]);
        printf("%.2lf\n",ans);
    }
    return 0;
}

Andrew演算法:

Andrew演算法是一種基於水平序的演算法,在許多的資料上都會發現說該演算法可以看作Graham掃描法的一種變體,為什麼這麼說呢?我的理解就是二者都是對所有的點進行掃描得到凸包,不過掃描之前做的處理不同,Andrew演算法的大致流程如下:

①將所有的點按照橫座標從小到大進行排序,橫座標相同則按縱座標從小到大排;

②將P[0]和P[1]加入凸包,然後從P[2]開始判斷,判斷方式同Graham演算法中的判斷一致;

③將所有的點掃描一遍以後,我們便可以得到一個“下凸包”(為什麼?畫個圖就懂了--橫座標不會減小);

④同理,我們從P[n-2]開始(P[n-1]已經判過了),反著掃描一遍,便可以得到一個“上凸包”;

⑤將兩個“半凸包”合在一起就是一個完整的凸包,注意的是由於起點P[0]在正著掃描和反著掃描時都會將其加入凸包,故需要將最後一個點(P[0])去掉才為最終結果。

具體程式碼如下:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=1e6+10;
const double esp=1e-10;
struct Point{
    double x,y;
    Point(){}
    Point(double _x,double _y){
        x=_x;y=_y;
    }
}P[maxn],Convexhull[maxn];
typedef Point Vector;
Vector operator + (Vector A,Vector B){
    return Vector(A.x+B.x,A.y+B.y);
}
Vector operator - (Vector A,Vector B){
    return Vector(A.x-B.x,A.y-B.y);
}
Vector operator * (Vector A,double d){
    return Vector(A.x*d,A.y*d);
}
Vector operator / (Vector A,double d){
    return Vector(A.x/d,A.y/d);
}
double Dot(Vector A,Vector B){
    return A.x*B.x+A.y*B.y;
}
double Cross(Vector A,Vector B){
    return A.x*B.y-A.y*B.x;
}
double Length(Vector A){
    return sqrt(Dot(A,A));
}
int dcmp(double x){
    return fabs(x)<esp?0:x<0?-1:1;
}
bool operator <(Point p1,Point p2){
    return dcmp(p1.x-p2.x)<0||(dcmp(p1.x-p2.x)==0&&dcmp(p1.y-p2.y)<0);
}
int Andrew(int n){      //Assume n>2
    sort(P,P+n);
    int top=0;
    for(int i=0;i<n;i++){
        while(top>1&&dcmp(Cross(P[i]-Convexhull[top-2],Convexhull[top-1]-Convexhull[top-2]))>=0)top--;
        Convexhull[top++]=P[i];
    }
    int k=top;
    for(int i=n-2;i>=0;i--){
        while(top>k&&dcmp(Cross(P[i]-Convexhull[top-2],Convexhull[top-1]-Convexhull[top-2]))>=0)top--;
        Convexhull[top++]=P[i];
    }
    return top-1;
}
int main(){
    freopen("in.txt","r",stdin);
    int n;
    while(~scanf("%d",&n)&&n){
        for(int i=0;i<n;i++)scanf("%lf%lf",&P[i].x,&P[i].y);
        int cnt=Andrew(n);
        double ans=0;
        if(cnt!=2)ans+=Length(Convexhull[0]-Convexhull[cnt-1]);
        for(int i=0;i<cnt-1;i++)ans+=Length(Convexhull[i]-Convexhull[i+1]);
        printf("%.2lf\n",ans);
    }
    return 0;
}

小結:

顯然兩種演算法的複雜度均為O(nlogn),若輸入有序的話時間複雜度就均為O(n),但是紫薯上說“和原始的Graham演算法相比,Andrew演算法更快,且數值穩定性更好”,或許是因為排序二者排序過程中的差異--Graham演算法中的極角排序需要進行大量的叉乘計算。

相關推薦

-Andrew演算法&&Graham掃描

凸包簡介: 在二維平面上(二維凸包)給出若干個點,能夠包含這若干個點的面積最小的凸多邊形稱為凸包(可以想像有很多個釘子釘在牆上,然後用一個橡皮圈套在所有的釘子上,最後橡皮圈形成的就是一個凸包)。 Graham掃描法: Graham掃描法是一種基於極角排序的進行求解的

計算幾何 : 學習筆記 --- Graham 掃描

 凸包 (只針對二維平面內的凸包) 一、定義 簡單的說,在一個二維平面內有n個點的集合S,現在要你選擇一個點集C,C中的點構成一個凸多邊形G,使得S集合的所有點要麼在G內,要麼在G上,並且保證這個凸多邊

(Convex Hull)構造演算法——Graham掃描

凸包(Convex Hull) 在圖形學中,凸包是一個非常重要的概念。簡明的說,在平面中給出N個點,找出一個由其中某些點作為頂點組成的凸多邊形,恰好能圍住所有的N個點。 這十分像是在一塊木板上釘了N個釘子,然後用一根繃緊的橡皮筋它們都圈起來,這根橡皮筋的形狀就是所謂的凸包。 計算凸包的一個著名演

計算幾何----Andrew演算法--HDU1392

題目描述 給出一些點,求凸包的周長。 什麼是凸包 用不嚴謹的話來講,給定二維平面上的點集,凸包就是將最外層的點連線起來構成的凸多邊型,它能包含點集中所有的點。 凸包的Andrew演算法 Andrew演算法是graham的變種。它的思想是這樣的:

演算法Graham掃描

目錄 一、概念 二、演算法步驟 三、程式碼實現 轉自:https://www.cnblogs.com/aiguona/p/7232243.html 一、概念 凸包(Convex Hull)是一個計算幾何(圖形學)中的概念。 在一個實數向量空間V中,對於給定集合X,所有

最小演算法(Convex Hull)(1)-Graham掃描 -計算幾何-演算法導論

基本問題: 平面上有n個點p1,p2, ..., pn, 要求求出一個面積最小的凸多邊形,使得這個多邊形包含所有平面上的點。 根據演算法導論上提供的兩個方法做一些介紹: 演算法1: Graham掃描法 下面直接給出一段虛擬碼,方便描述: GRAHAM-SCAN(Q) {

演算法詳解-Graham掃描

#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; struct nod

1015 - 計算幾何之Graham掃描 - Cows(POJ 3348)

傳送門   題意 給你一堆點,求這些點的凸包,並求出面積   分析 很久之前就做過的一道題了,還記得那是凱爺(凱爺好厲害好厲害的)講的,是Jarris步進法:按照橫縱座標對所有的點進行排序(橫座標優先) 然後就是和Graham類似的方法了,邊掃描邊

計算幾何之----Graham掃描

計算幾何之凸包(convexHull)----Graham掃描法 關於凸包的嚴格定義,這裡不打算寫出來,大家可以自行Google或者百度,因為嚴格的數學定義反而不太好理解,用最通俗的話來解釋凸包:給定

尋找Graham掃描

題意描述: 對任意給定的平面上的點集,求最小凸多邊形使得點集中的點要麼在凸多邊形的邊上,要麼在凸多邊形的內部。 Graham演算法描述: 在所有的點中找到一點p0,使得p0的縱座標值最小,在有多個最小縱座標的情況下,找橫座標最小的那一個。 將所有的點

模板(分治 or Graham掃描

問題概述:空間上有很多點,現在要用一個凸多邊形將所有點全部包住,求哪些點在這個凸多邊形上 輸入樣例:                                             對應輸出:

淺談Graham掃描

    凸包是計算幾何中的一個基本概念。在競賽中,很少單獨考察凸包,但求凸包是很多題目求解的一個關鍵性步驟。     1)凸包的性質         給定一個點集,凸包是能夠包圍所有點的最小凸多邊形。”凸包邊上的點,稱為凸包點,其餘點稱為凸包內點“(引自何援軍著《幾何計算

--Graham掃描

一直聽大佬們說:凸包、凸包、凸包 一直不會。。。。。 然後。。。。 今天考試,考了一道計算幾何的簡單題。。。。 這,,,還是學一下吧。。 然後考試現場學習一下凸包演算法。 先理解一下凸包是啥東西。 看看這張圖 解釋一下凸包是什麼 如果你有一堆點(原諒我畫的很凌亂) 那麼,找到一個點集 依次連線這些點 使他們

關於——Graham掃描

首先是凸包的第一種解法,graham掃描法。演算法的步驟如下: 1 首先我們要找到凸包的一個頂點,這個頂點的y座標要最小,相同的y的情況下,選擇更靠左的點 2 對給出的頂點集進行排序,按照極角遞增的順序進行排序?如何進行操作呢,我們可以用我們向量積的性質,如果向量積為正,b

Graham掃描->HDU3847

Graham掃描法求凸包 凸包定義: 點集Q的凸包(convex hull)是指一個最小凸多邊形,滿足Q中的點或者在多邊形邊上或者在其內。 凸包最常用的凸包演算法是Graham掃描法和Jarvis步進法。 Graham掃描法: 首先

matlab練習程式(尋找Graham掃描

  我不太清楚這個凸包在影象處理中到底會怎樣的運用,因為這個好像更多的是計算幾何或是圖形學裡面的東西。不過作為一個演算法,我感覺還是有必要研究一下的。我主要的參考資料是《演算法導論》的33.3和這個部落格。   程式碼在這裡,我只寫了主要過程,過分細節的判斷就省略了。這裡是逆時針尋找: main.m c

二維求 graham掃描

graham掃描法求凸包的做法就是,選擇一個座標最靠近左下的點,然後作為原點,其他點按極座標排序,角度由小到大,角度想等r小的優先,然後將前兩個點放到棧中進行初始化,之後順序對每一個點進行判斷,只有該點向左發生了偏轉才將該點入棧,否則刪掉當前棧頂元素,再次判斷,直到發生了向

問題—Graham掃描

#include<iostream> #include<cmath> #include<algorithm> using namespace std; struct point { long long x; long l

問題的Graham-Scan演算法及python實現

基於 Graham-Scan 的凸包求解演算法是在列舉三角形時,採用了更精細的方式,將P_0作為極點,通過極角大小定位最右下側的三角形∆P_0 P_1 P_2,然後讓三角形繞P_0點旋轉,掃描所有輸入點,直到到最左下側為止。 首先要對點集S進行預處理,

計算幾何入門 5:構造演算法下界

從極點法的O(n^4)複雜度,到極邊法的O(n^3),再到增量構造法和Jarvis March的O(n^2),我們經歷了將特定問題演算法不斷優化、降低複雜度的過程。那麼還有比O(n^2)更高效的演算法嗎?凸包構造演算法的下界是什麼?推廣到一般情況,在計算模型固定的情況下特定