8th 【计算几何】凸包

                                                凸包

【题目描述】:

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

【输入描述】:

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

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

【输出描述】:

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

【样例输入】:

8
2720 -443
46 -2422
-2077 -1346
4520 -4963
-1791 1547
-1262 4025
-2997 913
-1667 -2499

【样例输出】:

4520 -4963
2720 -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;  
}  



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值