凸包问题的描述(Graham法)

转自百度百科http://baike.baidu.com/view/707209.htm

Graham's Scan法

这个算法是由数学大师葛立恒(Graham)发明的,他曾经是美国数学学会(AMS)主席、AT&T首席科学家以及国际杂技师协会(IJA)主席。
问题
给定平面上的二维点集,求解其凸包。
过程
⒈ 在所有点中选取y坐标最小的一点H,当作基点。如果存在多个点的y坐标都为最小值,则选取x坐标最小的一点。坐标相同的点应排除。然后按照其它各点p和基点构成的向量<H,p>;与x轴的夹角进行排序,夹角由大至小进行顺时针扫描,反之则进行逆时针扫描。实现中无需求得夹角,只需根据余弦定理求出向量夹角的余弦值即可。以下图为例,基点为H,根据夹角由小至大排序后依次为H,K,C,D,L,F,G,E,I,B,A,J。下面进行逆时针扫描。(实现时可以将这些点依次入栈)。
 
⒉ 线段<H,K>;一定在凸包上,接着加入C。假设线段<K,C>;也在凸包上,因为就H,K,C三点而言,它们的凸包就是由此三点所组成。但是接下来加入D时会发现,线段<K,D>;才会在凸包上,所以将线段<K,C>;排除,C点不可能是凸包。
⒊ 即当加入一点时,必须考虑到前面的线段是否会出现在凸包上。从基点开始,凸包上每条相临的线段的旋转方向应该一致,并与扫描的方向相反。如果发现新加的点使得新线段与上线段的旋转方向发生变化,则可判定上一点必然不在凸包上。实现时可用向量叉积进行判断,设新加入的点为pn + 1,上一点为pn,再上一点为pn - 1。顺时针扫描时,如果向量<pn - 1,pn>;与<pn,pn + 1>;的叉积为正(逆时针扫描判断是否为负),则将上一点删除。删除过程需要回溯,将之前所有叉积符号相反的点都删除,然后将新点加入凸包。
在上图中,加入K点时,由于线段<H,C>要旋转到<H,K>的角度,为顺时针旋转,所以C点不在凸包上,应该删除,保留K点。接着加入D点,由于线段<K,D>要旋转到<H,K>的角度,为逆时针旋转,故D点保留。按照上述步骤进行扫描,直到点集中所有的点都遍历完成,即得到凸包。(最终堆栈中的点就是得到的凸包)
 
现学现卖:
问题描述,给定N个点,求这N个点的所围成凸包的面积。
提示:计算多边形的面积计算公式参考:http://wenku.baidu.com/link?url=e6Ipho1bnCT4WLrFClSVoltIUkoxFRkWsolwLXKc3a36_TFRg3dWaXdDloQxGzG6poL9i1dge9VhujzlnMu85VG6S5POC7VHhHQSurfwXky
 
 
计算两个向量夹角的cos值 公式:
(x1*x2+y1*y2)/ |A|.|B|
计算叉乘的公式:
x1*y2-x2*y1; 
 
 
所有代码如下:
// In Practice, You should use the statndard input/output
// in order to receive a score properly.
// Do not use file input and output. Please be very careful. 

#include <cstdio>
#include <stack>
#include <cmath>
#include <iostream>

using namespace std;

typedef struct _SPOT_{
    int x;
    int y;
} Spot;

int N;
Spot data[105];
double Answer;

double calc_cos(Spot Tag, Spot Base)
{
    double x1 = Tag.x - Base.x;
    double y1 = Tag.y - Base.y;
    double x2 = 1.0;
    double y2 = 0.0;
    /*x1*x2+y1*y2 == x1;
    *  |A| = sqrt(x1*x1 + y1*y1);  
    *  |B| =  1
    */
    return x1/ sqrt(x1*x1 + y1*y1);  
}

void sort(Spot data[], int N, int startPoint)
{
    int i,j;
    Spot t;
    /* data 第一个选择为起点值*/
    t = data[startPoint];
    data[startPoint] = data[1];
    data[1] = t;

    /* 按照顺时针排序*/
    for(i = 2; i<= N-1; i++)
    {
        for(j = i+1; j <= N; j++)
        {
            if ( calc_cos(data[i],data[1]) > calc_cos(data[j],data[1]) )
            {
                t = data[i];
                data[i] = data[j];
                data[j] = t;
            }
        }
    }
}

bool is_pop(Spot p1, Spot p2, Spot p3)
{
    double x1 = p2.x - p1.x;
    double y1 = p2.y - p1.y;

    double x2 = p3.x - p2.x;
    double y2 = p3.y - p2.y;

    
    /*计算向量的叉乘,逆时针扫描判断是否为负数,则将上一点删除*/
    double chaji = x1*y2-x2*y1;    
    if (chaji > 0)
        return true;
    else
        return false;
}

int main(int argc, char** argv)
{
    int tc, T, i, j;
    
    // The freopen function below opens input.txt file in read only mode, and afterward,
    // the program will read from input.txt file instead of standard(keyboard) input.
    // To test your program, you may save input data in input.txt file,
    // and use freopen function to read from the file when using cin function.
    // You may remove the comment symbols(//) in the below statement and use it.
    // Use #include<cstdio> or #include<stdio.h> to use the function in your program.
    // But before submission, you must remove the freopen function or rewrite comment symbols(//).

    // freopen("input.txt", "r", stdin);

    cin >> T;
    for(tc = 0; tc < T; tc++)
    {
        cin >> N;
        for(i=1;i<=N;i++) {
            cin >> data[i].x >> data[i].y;
        }
        
        Spot stackdata[105] = {0,};
        int seq[105] = {0,};        
        /**********************************
        *  Implement your algorithm here. *
        ***********************************/
        Answer = 0.0;
        /*find the start point*/
        int startPoint = 1;
        for ( i = 2; i <= N; i++ )
        {
            if(data[i].y < data[startPoint].y )
            {
                startPoint = i;
            }
            else if(data[i].y == data[startPoint].y )
            {
                if(data[i].x < data[startPoint].x)
                {
                    startPoint = i;
                }
            }
        }
        
        sort(data, N, startPoint);
        data[N+1] = data[1];
        /*just for test*/
        for (i = 1; i<=N; i++)
        {
            //cout << data[i].x << "   "<<data[i].y << endl;
        }
        
        int cnt = 0;
        stackdata[cnt++] = data[1];    //第一个进栈
        stackdata[cnt++] = data[2];    //第二个进栈        

        //回溯部分有误
        for( i = 3 ; i <= N; i++)
        {
            stackdata[cnt++] = data[i];            
            while (true == is_pop(stackdata[cnt-2], stackdata[cnt-1], data[i+1]))  //最新的三个点是否符合在凸包上的规则,不符合就弹出一个
            {
                    cnt--;                        
            }
        }

        stackdata[cnt]= data[1];        //一共有cnt+1个点 编号为0~cnt,编号为cnt和0的点相同    

        /*计算多边形的面积*/
        double ft;
        for (i = 0 ;i< cnt;i++)
        {
            ft = ( stackdata[i+1].x * stackdata[i].y-stackdata[i].x * stackdata[i+1].y)/2.0;
            Answer += ft;
        }

        // Print the answer to standard output(screen).
        cout.setf(ios::fixed);
        cout.precision(2);
        cout << Answer << endl;
    }

    return 0;//Your program should return 0 on normal termination.
}

 

 
 
 
 
 

posted on 2015-02-10 11:51  木星来客  阅读(925)  评论(0编辑  收藏  举报

导航