8th 【计算几何】凸包

来源:互联网 发布:sql内连接的意义 编辑:程序博客网 时间:2024/06/14 08:46

                                                凸包

【题目描述】:

平面上的N个点,求一个包含所有点的最小的凸多边形,这就是凸包问题了。这可以形象地想成这样:在地上放置一些不可移动的木桩,用一根绳子把他们尽量紧地圈起来,并且为凸边形,这就是凸包了。

【输入描述】:

第一行一个N,表示平面上的点数。

以下N行,每行一个x和y,表示一个点的横坐标和纵坐标。

【输出描述】:

输出最少的点形成的凸多边形,第一个点是y最小的点,如果y相同的点,x小的排在前面。

【样例输入】:

82720 -44346 -2422-2077 -13464520 -4963-1791 1547-1262 4025-2997 913-1667 -2499

【样例输出】:

4520 -49632720 -443-1262 4025-2997 913-1667 -2499

【时间限制、数据范围及描述】:

时间:1s 空间:128M

N<=100000 -10000<=x,y<=10000

所谓凸包,可想象为一条刚好包著所有点的橡皮圈。


        因为不方便作图,我大致用文字叙述下方法。
        首先找到一个最低偏左点,然后按照延此点的角度大小顺序开始扫描,如果该点不会影响凸性,就继续扫描,否则就把已经找到的最后一个点删去,然后在判断是否影响凸包的凸性。(这里的实现需要用栈)
         那么如何判断该点是否可取呢,这就是叉积运用了,通过叉积判断某线段在已知线段的顺时针方向或是逆时针方向。
       (这是计算几何的知识,可以参考网上一些关于叉积的详细描述) 
         关于极角排序,也是关于叉积的知识,按极角排序,如果极角相同,按距离小大排序。

凸包的具体过程可以参考下面过程(图很清楚)

原文链接:https://segmentfault.com/a/1190000000488339;作者: Michael_Lin


首先介绍一下二维向量的叉积(这里和真正的叉积还是不同的):对于二维向量a=(x1,y2)和b=(x2,y2),a×b定义为x1*y2-y1*x2。而它的几何意义就是|a||b|sin<a,b>。如果ab夹角小于180度(逆时针),那么这个值就是正值,大于180度就是负值。需要注意的是,左乘和右乘是不同的。如图所示:


Graham Scan算法的做法是先定下一个起点,一般是最左边的点和最右边的点,然后一个个点扫过去,如果新加入的点和之前已经找到的点所构成的“壳”凸性没有变化,就继续扫,否则就把已经找到的最后一个点删去,再比较凸性,直到凸性不发生变化。分别扫描上下两个“壳”,合并在一起,凸包就找到了。这么说很抽象,我们看图来解释:

我们找下“壳”,上下其实是一样的。首先加入两个点A和C:


然后插入第三个点G,并计算AC×CG的叉积,却发现叉积小于0,也就是说逆时针方向上∠ACG大于180度,于是删去C点,加入G点:


然后就是依照这个步骤便能加入D点。在AD上方是以D为起点。就能够找到AGD和DFEA两个凸壳。合并就得到了凸包。



上述过程是很清楚的,关于叉积如果有不理解的可以上网查一下,数学知识我就不解释了(呵呵主要是解释不好)


下面是我写的程序

#include <iostream>  #include <cstring>  #include <cstdio>  #include <algorithm>  #include <cmath>using namespace std;  int n,top;struct node{    int x;    int y;    };node a[100005],ans[100005],start; // a为原数组,ans为栈,start为起点。    int chaji(node p1,node p2,node p0){return(p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);}//计算叉积   void findmin(){      start.x=2147483647;      start.y=2147483647;      for(int i=1;i<=n;i++)         if((a[i].y<start.y)||(a[i].y==start.y&&a[i].x<start.x))               {   start.x=a[i].x;              start.y=a[i].y;}          }//寻找最低偏左的点,就是起点。     int cmp(node a,node b){    if(chaji(a,b,start)==0)    {   int disa=(a.x-start.x)*(a.x-start.x)+(a.y-start.y)*(a.y-start.y);        int disb=(b.x-start.x)*(b.x-start.x)+(b.y-start.y)*(b.y-start.y);        return (disa<disb);               }    return (chaji(a,b,start)>0);    }         //极角排序,先按角度,后按距离      void tubao(){ //中心程序,从第三个点开始扫描     ans[1]=start;    ans[2]=a[2];    top=2;    for(int i=3;i<=n;i++)    {   while(chaji(a[i],ans[top],ans[top-1])>=0&&top>0)//判断该点是否改变凸包,如果可以就再往回走一个点,再判断         top--;        ans[++top]=a[i]; //每次最终连接两点         }    }       int main()  {     freopen("test1.in","r",stdin);      freopen("test1.out","w",stdout);      //清晰的操作过程       cin>>n;      for(int i=1;i<=n;i++)        cin>>a[i].x>>a[i].y;//读入         findmin();//寻找起点       sort(a+1,a+n+1,cmp);  //排序       tubao();//扫描找凸包       for(int i=1;i<=top;i++)      cout<<ans[i].x<<" "<<ans[i].y<<endl;//将栈里元素输出      return 0;  }