凸包小结 数学:凸包算法详解

先说部分资料来源(蒟蒻也是从他们那里学会的):html

数学:凸包算法详解——爱国呐node

计算几何之凸包(convexHull)----Graham扫描法——天泽28ios

话说原本在学斜率优化DP,结果由于某位坑爹博主的一句原本没有问题的话:算法

是否是很像一个下凸包? 
咱们用当前的斜率k从下方去不断逼近下凸包,最终会先碰到哪个点? post

我莫名其妙的去学了凸包,以为学完以后斜率DP说不定能好作点,可是。。。。。。如今依然看不明白。优化

那么进入正题,先看这样一个例题:url

mhr:明显,这个题咱们能够暴力枚举!spa

博主:滚!.net

能够发现,暴力是绝对不可取的,其实若是这个图就摆在你面前的话,你是很容易就能看出来,栅栏长度就是最外面的一圈点,可是电脑并无眼睛,因此咱们就要使用一些算法来计算出来那外面的一圈,也就是所谓的凸包。有一个比较学术的说法,来自某度某科:3d

凸包(Convex Hull)是一个计算几何(图形学)中的概念。
在一个实数向量空间V中,对于给定集合X,全部包含X的凸集的交集S被称为X的凸包。X的凸包能够用X内全部点(X1,...Xn)的凸组合来构造.
在二维欧几里得空间中,凸包可想象为一条恰好包著全部点的橡皮圈。
用不严谨的话来说,给定二维平面上的点集,凸包就是将最外层的点链接起来构成的凸多边型,它能包含点集中全部的点。

咱们有不少不一样的方法求出凸包,不过这里介绍的是性价比很高的Graham算法。

graham算法的整个操做基本都在一个栈中完成。若是设全部点的集合为点集Q,那么Q中的全部点都要入栈一次,而后再把一部分不符合要求的点弹出栈,最后剩在栈中的点就是凸包上的点了。

具体实现步骤以下

  1. 首先咱们要选取一个基点o,要求在点集Q中,基点o纵坐标必须是最小的,若是有相同最小的纵坐标,那么选取横坐标最小的,若是还有相同的。。确定是重点,不用管它就是了。如何快速找到?若是你不嫌麻烦,大能够sort排序以后选第一个,然而实际上,咱们只须要找出那个最下面同时是最左边的点就能够了,由于以后整个点集还会从新排序,因此这一开始的顺序没什么用。
  2. 而后咱们把剩下的全部点以o为极点,进行极角排序,角小的放在前面,若是角度相同,那么按照到点o的距离排序。

  3. 设全部的点事p1,p2.....pn。将o,p1,p2三个点压入栈,开始遍历全部剩下的点。

  4. 对每个新遍历到的点,很明显咱们须要逆时针旋转当前方向,若是有一个点顺时针旋转了,那么咱们就把栈顶的点弹出,直到符合逆时针旋转这个要求为止。

看上去十分简明扼要易懂是否是???

读者:打死这个博主,写这些东西我能看懂啥??那个顺时针逆时针我能看出来,电脑那个愚蠢的东西咋看出来??

判断顺时针仍是逆时针旋转,咱们要用到一个东西——叉积。

好吧又一个新名词。某度某科这么定义叉积:

向量积,数学中又称外积、叉积,物理中称矢积、叉乘,是一种在向量空间中向量的二元运算。与点积不一样,它的运算结果是一个向量而不是一个标量。而且两个向量的叉积与这两个向量和垂直。其应用也十分普遍,一般应用于物理学光学和计算机图形学中。

嗯。。。这其实什么也没说明白,这么说吧,貌似博主们和笔者都是一致认为:叉积a×b是点0,a,b和a+b组成的平行四边形的向量面积(也就是有方向的面积)。若是计算出来的叉积是正,那么a在b右侧,不然a在b左侧。若是增添一个公共端点c,那么计算方法就是:(c-a)×(c-b)。P.s可能说的不是很明白,由于笔者对于插入数学公式这种操做仍是略不熟练,文章最上方的dalao的博客里有比较清晰地证实。

那么给出算法导论里的证实,仍是比较明白的:

 

 然而其实也有瑕疵,就是这个正负和你计算的顺序是有关系的,常常有不等号写反结果一直爆0的现象发生。因此有句老话“尽信书则不如无书”,不要过度相信书上说的,多实践才是真理。

其实应该手绘静态步骤图的,可是确实是没那个实力,博主从小学开始美术就连B都没得过,全是CD。。。。

那么借来一张动态的给你们看一眼吧:

 

 。

那么基本上大致的就说完了,接下来就是代码了。与往常不一样,博主此次会加详细的注释诶:

p.s:以上面那道题目为例。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define ll long long
#define inf 50000000
#define re register
#define MAXN 50005
using namespace std;
struct node{
    double x,y;
};
node a[MAXN],stackk[MAXN];
double xx,yy;
int n,top;
inline int read()
{
    int x=0,c=1;
    char ch=' ';
    while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
    while(ch=='-') c*=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
    return x*c;
}
inline double js(node a,node b)//计算距离自没必要说 
{
    return sqrt((a.x-b.x)*(a.x-b.x)*1.0+(a.y-b.y)*(a.y-b.y)*1.0);
}
inline bool cmp(node a,node b)//第一遍排序,来求基点。 
{
    if(a.y==b.y) return a.x<b.x;
    return a.y<b.y;
}
inline double cross(node a,node b,node c)//计算以a为公共端点,b与c的叉积。 
{
    return (b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y);
}
inline bool cmp1(node a,node b)//极角排序 
{
    double k=cross(stackk[1],a,b);
    if(k>0) return 1;//若是b在a时针方向返回1 
    else if(k==0) return js(stackk[1],a)<=js(stackk[1],b);//若是极角相等,则比较距离 
    else return 0;
}
int main()
{
    n=read();
    for(re int i=1;i<=n;i++){
        scanf("%lf%lf",&a[i].x,&a[i].y);
    }
    if(n==1) {printf("0");return 0;}
    if(n==2) {printf("%.2lf",js(a[1],a[2]));return 0;}
    sort(a+1,a+n+1,cmp);
    stackk[++top]=a[1];
    xx=stackk[top].x;
    yy=stackk[top].y;
    sort(a+2,a+n+1,cmp1);
    stackk[++top]=a[2]; 
    stackk[++top]=a[3];//把p1,p2,p3压入栈中。
    for(re int i=4;i<=n;i++){
        while(top>0&&cross(stackk[top-1],stackk[top],a[i])<0)//若是右旋转了,就弹出栈顶的点 
        top--;
        stackk[++top]=a[i];//加入新点 
    }
    double ans=0;
    for(re int i=2;i<=top;i++)//点之间两两求距离。 
    ans+=js(stackk[i-1],stackk[i]);
    ans+=js(stackk[top],stackk[1]);
    printf("%.2lf",ans);
}

其实也不是很详细了。。。不过笔者自认为码风清晰易懂(逃~~

相关文章
相关标签/搜索