1. 程式人生 > >淺談凸包之Andrew 與 Graham

淺談凸包之Andrew 與 Graham

前言

腦補知識點:

1.向量的內積(數量積,點乘):

公式:a· b = |a| * |b| cos<a, b>a.x* b.y + b.x * a.y

2.向量的外積(向量積,差乘):

公式:|c|= |a|*|b|*sin<a, b> = a.x * b.y - b.x * a.y

點在多邊形內判定

多邊形: 就是二維平面上被一系列首尾相接、閉合的折線段圍成的區域 在程式中一般用定點陣列表示 其中各個定點按照逆時針順序排序

問: 給你一個點 如何判斷它是在多邊形內 呢?

1.射線法 :從判斷點出發,任意引一條射線 如果和邊界相交奇數次 就在多邊形內 偶數次 在外面

2.轉角法: 基本思想 看多邊形相對這個點轉了多少度 具體說來就是我們把每個轉角加起來 如果為360 在內 否則在外(具體程式碼劉汝佳的書上有 也可自行寫一個)

凸包

凸包 : 把給定的點包圍在內部的、面積最小的凸多邊形。

兩種演算法:andrew演算法 和 graham

演算法總思想: 

找一個凸包上的點,把這個點放到第一個點的位置P0。然後把P1~P按照P0Pi的方向排序

Graham

來源: 百度

Graham掃描法

基本思想:通過設定一個關於候選點的堆疊s來解決凸包問題。

操作:輸入集合Q中的每一個點都被壓入棧一次,非CH(Q)(表示Q的凸包)中的頂點的點最終將被彈出堆疊,當演算法終止時,堆疊S中僅包含CH(Q)中的頂點,其順序為個各頂點在邊界上出現的逆時針方向排列的順序。

注:下列過程要求|Q|>=3,它呼叫函式TOP(S)返回處於堆疊S 頂部的點,並呼叫函式NEXT-TO –TOP(S)返回處於堆疊頂部下面的那個點。但不改變堆疊的結構。

GRAHAM-SCAN(Q)

1           設P0 是Q 中Y 座標最小的點,如果有多個這樣的點則取最左邊的點作為P0;

2           設<P1,P2,……,Pm>是Q 中剩餘的點,對其按逆時針方向相對P0 的極角進行排序,如果有數個點有相同的極角,則去掉其餘的點,只留下一個與P0 距離最遠的那個點;

3           PUSH(p0 , S)

4           PUSH(p1 , S)

5           PUSH(p3 , S)

6           for i ← 3 to m

7               do while 由點NEXT-TOP-TOP(S),TOP(S)和Pi 所形成的角形成一次非左轉 

8                   do POP(S)

9               PUSH(pi , S)

10return S 

首先,找一個凸包上的點,把這個點放到第一個點的位置P0。然後把P1~P按照P0Pi的方向排序,可以用向量積(叉積)判定。 

做好了預處理後開始對堆疊中的點<p3,p4,...,pm>中的每一個點進行迭代,在第7到8行的while迴圈把發現不是凸包中的頂點的點從堆疊中移去。(原理:沿逆時針方向通過凸包時,在每個頂點處應該向左轉。因此,while迴圈每次發現在一個頂點處沒有向左轉時,就把該頂點從堆疊中彈出。)當演算法向點pi推進、在已經彈出所有非左轉的頂點後,就把pi壓入堆疊中。

圖片來源:http://kmplayer.iteye.com/blog/604405

                                  

 
#include<iostream>//凸包求點
#include<stack>
#include<algorithm>
#include<string.h>
#include<math.h>
#include<stdio.h>
#define INF 9999999999
#define eNs 1e-6
#define MAX 105

using namespace std;

struct node
{
    int x,y;
};
node N[MAX],cHull[MAX],N0,stk[MAX];

int m,n;
int cnt;
int cross(node a,node b,node c)
{
    return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}

int dis(node a,node b)
{
    return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}

bool cmN(node a,node b)
{
    int t=cross(N0,a,b);
    return t>0||(t==0 && dis(N0,a)<dis(N0,b));
}
void convexHull()
{
    int i,j,k;
    m=0;
    cnt=0;
    for(k=0,i=0;i<n;i++)
        if(N[i].y<N[k].y||(N[i].y==N[k].y && N[i].x<N[k].x) )
            k=i;
    N0=N[k];
    N[k]=N[0];
    N[0]=N0;
    sort(N+1,N+n,cmN);
    stk[0]=N[0];
    stk[1]=N[1];
    int top=1;
    for(i=2;i<n;i++)
    {
        while(top && cross(stk[top-1],stk[top],N[i])<=0)
            {
                    top--;
            }
        stk[++top]=N[i];
    }
    m=top+1;
}
bool ccmp(node a,node b){
    if(a.y==b.y)return a.x<b.x;
    return a.y>b.y;
}
int cmp(node a,node b)
{
    if(a.x != b.x)
        return a.x <b.x;
    else
        return a.y < b.y;
}
int main()//就是一模板提
{
    int t;
    cin>>t;
    while(t--)
    {
        int k;
        int i,j;
        cin>>n;
        for(i=0;i<n;i++)
        cin>>N[i].x>>N[i].y;
        convexHull();
        int xx=stk[0].x,yy=stk[0].y;
        int tag=0;
        for(i=1;i<m;i++)
             if(stk[i].y>stk[tag].y || (stk[i].y==stk[tag].y && stk[i].x<stk[tag].x))
               tag=i;
//        printf("%d %d\n",k,m);
//        for(i=tag;i>=0;i--)
//        printf("%d %d\n",stk[i].x,stk[i].y);
//        for(i=m-1;i>tag;i--)
//        printf("%d %d\n",stk[i].x,stk[i].y);
        sort(stk,stk+m,cmp);
        for(int i=0;i<m;i++)
            cout<<stk[i].x<<" "<<stk[i].y<<endl;
    }
    return 0;
}
        


Andrew演算法

我學的這個: 他是graham演算法的變種 比graham要快 具體實現: 1.把所有點按照x從小到大進行排序 (x同則y用y進行排序) 2.刪除重複序列後得到序列p1,p2.。。, 3.讓後把p1 p2放入凸包中 從p3開始。當新點在凸包“前進”方向的左邊時繼續,否則依此刪除最近加入凸包的點 直到新點在左邊 這個演算法你可以理解為逆時針畫圓  每次半個圓


例題:

圈水池

時間限制:3000 ms  |  記憶體限制:65535 KB 難度:4
描述
有一個牧場,牧場上有很多個供水裝置,現在牧場的主人想要用籬笆把這些供水裝置圈起來,以防止不是自己的牲畜來喝水,各個水池都標有各自的座標,現在要你寫一個程式利用最短的籬笆將這些供水裝置圈起來!(籬笆足夠多,並且長度可變)
輸入
第一行輸入的是N,代表用N組測試資料(1<=N<=10)
第二行輸入的是m,代表本組測試資料共有m個供水裝置(3<=m<=100)
接下來m行代表的是各個供水裝置的橫縱座標
輸出
輸出各個籬笆經過各個供水裝置的座標點,並且按照x軸座標值從小到大輸出,如果x軸座標值相同,再安照y軸座標值從小到大輸出
樣例輸入
1
4
0 0
1 1
2 3
3 0
樣例輸出
0 0
2 3
3 0
來源
上傳者
張潔烽

 
#include<bits/stdc++.h>
using namespace std;
struct point
{
    int x,y;
};
point operator +(point A,point B)//
{
    point C;
    C.x = A.x+B.x;
    C.y = A.y+B.y;
    return C;
}
point operator -(point A,point B)
{
    point C;
    C.x = A.x-B.x;
    C.y = A.y-B.y;
    return C;
}

int Cross(point A,point B)//叉積
{
    return A.x*B.y-A.y*B.x;
}

int cmp(point A,point B)
{
    return A.x==B.x ? A.y<B.y : A.x<B.x;
}

int ConvexHull(point *p,int n,point *ch)//Andrew
{
    int m = 0;
    for(int i = 0;i < n;i++)
    {
        while(m>1&&Cross(ch[m-1] - ch[m-2],p[i]-ch[m-2]) <= 0) m--; //如果發現更好的點把之前凸包內的點吐出 
        ch[m++] = p[i];
    }
    int k = m;
    for(int i=n-2;i >= 0;i--)
    {
        while(m > k&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2]) <= 0) m--;
        ch[m++] = p[i];
    }
    if(n>1)
        m--;
    //cout << m << endl;
    return m;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        point p[105],ch[105];
        for(int i = 0;i < n;i++)
            scanf("%d%d",&p[i].x,&p[i].y);

        sort(p,p+n,cmp);
        int m = ConvexHull(p,n,ch);
//        printf("m: %d\n",m);
        sort(ch,ch+m,cmp);
        for(int i = 0;i < m;i++)
            printf("%d %d\n",ch[i].x,ch[i].y);
    }
}