凸包
【题目描述】:
平面上的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>。如果a与b夹角小于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;
}