凸包入门

本文介绍信息学奥赛中计算几何的重要部分——凸包算法。重点讲解葛立恒扫描法,包括寻找初始顶点、点排序、顶点栈处理及计算凸包周长与面积的方法。

8月9日 初稿出炉
8月17日 第一次更新 凸包面积

凸包入门

凸包是信息学奥赛的计算几何里面一个比较重要的一块儿。今天写一下一些有关于凸包的入门内容。

什么是凸包

顾名思义,听见凸包的名字,大家大概对它的形状有一定的认识了。而凸包里面“包”的是什么呢?是一堆点。
就像这样。
就是一个凸凸的包把一堆点全包住啦
值得一提的是,凸包的各个顶点都是这些点里面的几个。
这里写图片描述

怎么解凸包呢

今天主要讲讲葛立恒(Ronald Graham,the US)扫描法。
比较显然的一个前提是,在保证纵坐标值最小的前提下,横坐标尽可能小的点只有一个,也就是上面图里面的蓝色点。而这个点一定是凸包的一个顶点,我们令它为1号点。
处理所有的顶点总需要一个顺序吧,那么我们就以这个顶点为中心,逆时针或者顺时针的扫一遍所有的点作为预处理。

叉积

在讲预处理之前,先要了解一下向量的外积(叉积)的性质,对于向量P(a,b)与Q(c,d),它的叉积记作P x Q=ad-bc
判断两个向量之间的顺逆关系
若结果为正,则向量Q在P的逆时针方向
否则,在P的顺时针方向
若结果为0,则P与Q共线

排序

之后我们就可以逆时针的开始排序了。
疑惑:不过话说网上的解答里面并没有解释从哪里开始逆时针转啊。其实从哪里开始是无所谓的,因为从哪里开始都需要转整整一周才行,都能实现遍历。
判断叉积是否大于0,即可判断两个从出发点出发的向量之间的位置关系,也就可以判断所有点的扫描顺序了。sort排序的cmp如下。在这里我们按照逆时针做。

int cmp(data p,data q)
{
    double mul=cj(a[1],p,a[1],q);
    if(mul>0) return 1;
    return 0;
}
顶点的栈

由于三角形必然是凸多边形,所以我们先把出发点1,还有标号为2、3的这三个点放入栈中,检查每一个即将加入栈的点与上一点形成的向量和上两个点之间组成的向量之间是否是逆时针关系即可。如果仍然是逆时针则舍弃,反之则保留入栈,以此类推。

凸包的周长

从栈中一一弹出,计算相邻两点间距并放入累加器即可,最后再把原来栈顶和栈底的两点间距加上,这样才能形成封闭的图形。刚才所讲的“间距”用常见的距离公式即可得出(欧几里得距离),用勾股定理即可。

凸包的面积(一更)

先提一下向量叉积的几何意义,叉积数值上等于两个向量共起点的情况下组成的平行四边形的面积(方向要用右手定则去判断),我们把原点与每一个顶点相连,会构成一组三角形,这些三角形每一个的面积显然就是 原点p0与pi组成的向量 和 pi与pi+1组成的向量 所构成的平行四边形面积的一半,所以我们只需要一个个的求算每一组的叉积的一半,并放入累加器中即可。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int i,j,m,n;
int ans[10001];

struct data
{
    double x,y;
}a[10001];
int r()
{
    int ans=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')
    {
        ans*=10;
        ans+=ch-'0';
        ch=getchar();
    }
    return ans; 
}

double mx,my=1000000;

double cj(data a1,data a2,data b1,data b2)
{
    return (a2.x-a1.x)*(b2.y-b1.y)-(b2.x-b1.x)*(a2.y-a1.y);
}

int cmp(data p,data q)
{
    double mul=cj(a[1],p,a[1],q);
    if(mul>0) return 1;
    return 0;
}

double dis(data p,data q)
{
    return sqrt((p.x-q.x)*(p.x-q.x)+(p.y-q.y)*(p.y-q.y));
}

int main()
{
    n=r();
    for(i=1;i<=n;i++)
    {
        scanf("%lf",&a[i].x);
        scanf("%lf",&a[i].y);
        if(a[i].y<my||a[i].y==my&&a[i].x<mx)
        my=a[i].y,mx=a[i].x,m=i;
    }

    double xx,yy;
    xx=a[m].x,yy=a[m].y;
    a[m].x=a[1].x,a[m].y=a[1].y;
    a[1].x=xx;a[1].y=yy;

    sort(a+2,a+n+1,cmp);

    int tot=3;
    ans[1]=1,ans[2]=2,ans[3]=3;
    for(i=4;i<=n;i++)
    {
        while(cj(a[ans[tot-1]],a[ans[tot]],a[ans[tot]],a[i])<=0)
        tot--;
        ans[++tot]=i;
    }

    double sum=0;
    for(i=1;i<tot;i++)
    sum+=dis(a[ans[i]],a[ans[i+1]]);

    sum+=dis(a[ans[tot]],a[1]);
    printf("%.2lf",sum);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值