凸包--Graham扫描法
一直听大佬们说:凸包、凸包、凸包
一直不会。。。。。
然后。。。。
今天考试,考了一道计算几何的简单题。。。。
这,,,还是学一下吧。。
然后考试现场学习一下凸包算法。
先理解一下凸包是啥东西。
看看这张图
解释一下凸包是什么
如果你有一堆点(原谅我画的很凌乱)
那么,找到一个点集
依次连接这些点
使他们形成一个凸多边形
并且所有的点都包括在这个多边形的内部或者边上
这个多边形就是一个凸包(我写的肯定一点也不严谨)
不管怎么样,就先这样理解一下吧。。。。。。
凸包是啥应该不难理解,那么,给你一堆点,怎么求凸包?
这种东西。。。。。
先大概说一下把。。。
首先找到最靠近左下的那个点,这个点一定在凸包上(不难理解吧。。。画个图就知道了)
以这个点为极点,其他点按照极角排序
然后按照顺序依次访问所有点,判断可行性
这样子干说真是虚无缥缈的东西。。。。。。
画图来解释
这是一片点。
找到最靠近左下的一个点
其他的点按照极角排序
然后把1丢到凸包的栈里面,准备开始扫描
检查2号点是否在1的一侧,(检查一下是不是凸多边形)
这里检查到2号可行,先加入到栈中
检查到3更加靠近外侧(如果加入3号就会形成凹多边形,显然3在凸包中,而2不在)
然后把2号点弹出栈,判断1号和3号节点的关系(同判断2号)
依次这么判断,最后所有凸包上的点都会在栈中
这样子算法的步骤很显然了。
继续解决一些细节上的问题(貌似就一个把。。。。)
怎么计算一个节点是否在前一个点的一侧。。。。
(我说的好不专业。。。我自己都不知道该怎么说一些名词。。。就将就着理解一下吧。。。)
我们先拿几个点出来
其中1,2,3是当前在凸包的栈中的点,4号节点是需要判断的点
那么,我们需要从栈中拿最上方的两个点(2和3节点)
把他们连接起来,再把2和4连接起来(怎么连接?我是不会说直接用向量的坐标表示就可以了)
计算一下两个向量的叉积。。
哈,叉积。。。
解释一下吧。。
假设2到3的向量是a(x1,y1)
2到4的向量是b(x2,y2)
那么,计算一下它们的叉积,也就是x1y2-x2y1
换种方法来表示就是。
|a|·|b|·sin<a,b>
(所以说叉积也可以用来求出三角形的面积~这个以后还会用到的)
如果,这两个向量的叉积≥0 证明这两个向量平行或者夹角是个锐角
也就证明了3号节点此时一定不再凸包上(因为连接2和4之后3在凸包内侧了)
把3号节点弹出栈,继续重复上面的步骤即可。
感觉我说的有点小复杂诶。。。。
这个东东多画点图就会理解的
如果还是不太清楚,可以看一看代码。
struct Node
{
int x,y;
}p[MAX],S[MAX];//p储存节点的位置,S是凸包的栈
inline bool cmp(Node a,Node b)//比较函数,对点的极角进行排序
{
double A=atan2((a.y-p[1].y),(a.x-p[1].x));
double B=atan2((b.y-p[1].y),(b.x-p[1].x));
if(A!=B)return A<B;
else return a.x<b.x; //这里注意一下,如果极角相同,优先放x坐标更小的点
}
long long Cross(Node a,Node b,Node c)//计算叉积
{
return 1LL*(b.x-a.x)*(c.y-a.y)-1LL*(b.y-a.y)*(c.x-a.x);
}
void Get()//求出凸包
{
p[0]=(Node){INF,INF};int k;
for(int i=1;i<=n;++i)//找到最靠近左下的点
if(p[0].y>p[i].y||(p[0].y==p[i].y&&p[i].x<p[0].x))
{p[0]=p[i];k=i;}
swap(p[k],p[1]);
sort(&p[2],&p[n+1],cmp);//对于剩余点按照极角进行排序
S[0]=p[1],S[1]=p[2];top=1;//提前在栈中放入节点
for(int i=3;i<=n;)//枚举其他节点
{
if(top&&Cross(S[top-1],p[i],S[top])>=0)
top--;//如果当前栈顶不是凸包上的节点则弹出
else S[++top]=p[i++];//加入凸包的栈中
}
//底下这个玩意用来输出凸包上点的坐标
//for(int i=0;i<=top;++i)
// printf("(%d,%d)\n",S[i].x,S[i].y);
}
接下来找一道简单点的例题
HDU 1392
这道题目就是求出凸包然后计算周长,很简单的题目,去试试吧。。