二维凸包详解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/tylon2006/article/details/96454684

凸包大意:给一堆点,让你求一个最小的多边形使得其囊括所有点。

实际上就是拿条绳子在一堆钉子外面绕一圈,绕成的图形就是所求多边形,即绕绳法。
但这里并不讨论绕绳法,而是介绍一种叫Graham的算法。


前置芝士:叉积(a×b=a.x·b.y+a.y·b.x)

叉积不同于点积,叉积的结果是向量,而点积的结果是标量。
设平面直角坐标系有三点A(x0,y0),B(x1,y1),C(x2,y2)。
有共起点p0的两个向量AB(x1-x0,y1-y0),AC(x2-x0,y2-y0),它们的叉积表示为AB×AC=(x1-x0)•(y2-y0)+(y1-y0)•(x2-x0)。

此时有三种情况:
  • 若p0p1×p0p2>0,那么向量p0p1在向量p0p2的逆时针方向;
  • 若p0p1×p0p2<0,那么向量p0p1在向量p0p2的顺时针方向;
  • 若p0p1×p0p2=0,那么向量p0p1与向量p0p2共线。

如下图,a为p0p1,b为p0p2。

至于为什么请读者自行百度思考(其实我也不知道)。

另一个用途:

若有向量AB,AC,则AB×AC=2S∆ABC。
这样就可以把求出来的多边形分割成一个个三角形,再统计这些三角形的总面积,就是多边形的面积。

在这里插入图片描述

算法:

Graham算法的实现步骤如下:

  • 设stk数组为答案栈。
  • 先取一个最左下的点(下优先),易知这个点为所求多边形上一个点,可以直接入栈。以这个点对其余n-1个点做极角排序,这样可以保证是逆时针确定所求多边形的边。
    极角排序:按夹角的大小排序。
  • 接下来枚举剩下的n-1个点:设起点为stk[n-1],终点为stk[n]即栈顶的向量为a,起点为stk[n-1],终点为当前点的向量为b。用叉积检查a是否在b的逆时针方向(多边形的边尽量靠外即尽量靠顺时针才能包住所有点),如果a在b的逆时针方向(或共线)就将stk[n]出栈,重复执行该操作,直到不满足条件退出。最后加入新点。
    tip:共线的情况视题目条件而定。
  • 枚举完后已经得到了答案数组stk,可以为所欲为了

大概长这个样:

在这里插入图片描述





模板

#include<bits/stdc++.h>
using namespace std;
int n,tot=1,xx,yy;
struct data{
    double x,y;
};
data nd[60060];
data stk[60060];
double ans=0,mid;
double cross(data a,data b,data c){
    return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
double dis(data a,data b){
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
bool cmp2(data a,data b){//极角排序另一种方法,速度快
    if(atan2(a.y-yy,a.x-xx)!=atan2(b.y-yy,b.x-xx))
    return (atan2(a.y-yy,a.x-xx))<(atan2(b.y-yy,b.x-xx));
    return dis(nd[1],a)<dis(nd[1],b);
}
bool cmp1(data p1,data p2){//稳定 
    double tmp=cross(nd[1],p1,p2);
    if(tmp>0) return 1;
    if(tmp==0 && dis(nd[1],p1)<dis(nd[1],p2)) return 1;
    return 0;
}//按照夹角的大小排序 
bool cmp(data a,data b){//排序找第一个点
    if(a.y==b.y)
        return a.x<b.x;
    else
        return a.y<b.y;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i){
        scanf("%lf%lf",&nd[i].x,&nd[i].y);
        if(nd[i].x<nd[1].x||(nd[i].x==nd[1].x&&nd[i].y<nd[1].y)){
            mid=nd[1].y;nd[1].y=nd[i].y;nd[i].y=mid;
            mid=nd[1].x;nd[1].x=nd[i].x;nd[i].x=mid;
        }
    } 
    stk[1]=nd[1]; 
    xx=nd[1].x,yy=nd[1].y;
    sort(nd+2,nd+n+1,cmp1);
    for(int i=2;i<=n;i++){
        while(tot>1&&cross(stk[tot-1],stk[tot],nd[i])<=0) tot--;
        stk[++tot]=nd[i];
    }
    stk[tot+1]=nd[1];
    //求周长
    for(int i=1;i<=tot;i++)
    ans+=dis(stk[i],stk[i+1]);
    /*求面积
    for(int i=1;i<=tot;i++)//每次找俩点与第一个点连边组成三角形
    ans+=cross(stk[1],stk[i],stk[i+1])/2.0;
    */   
    printf("%.2lf",ans);
}

题目

待续。。。

猜你喜欢

转载自blog.csdn.net/tylon2006/article/details/96454684