昨天在做ZOJ 3537的时候,遇到了凸包的判定,用之前的面积判定计算的时候,总是错误,所以学习了一下凸包的判定算法
对我这个几何一点都不会的人来说,叉积判定两个向量方向的地方,只是能记住定理,到现在还是没明白为什么会是这样......
叉积
a × b > 0 则向量a在向量b的顺时针方向
a × b < 0则向量a在向量b的逆时针方向
a × b = 0 则向量a与向量b共线
Graham算法解释
算法步骤:
1、首先找打最左下角的点a,也就是y最小,如果y相同,就去x较小的
2、以a点为基点求出其他所有点的极角,按照极角从小到大排序(这里就可以使用上面叉积的性质,当叉积大于零时,那么a的极角就小于b的极角,因为a在b的顺时针方向)
3、将a,以及极角最小的节点放入队列,按照顺序极角增长的顺序,每次取一个新的节点c,和队列中两个点a(较前面的节点)、b作比较,如果向量bc × 向量ac >= 0 说明:bc在ac顺时针方向或者同方向,那么这时b节点就不能取,因为c节点在b节点的相对较外的位置,b节点删除,这样一直比较,直到c节点处于相对较内的位置,将c节点放入队列;如果向量bc × 向量ac <0 说明:bc在ac逆时针方向,那么b节点是可取的,c节点直接放入队列
4、最终队列中的点就是凸包上的节点
模板
#include <iostream> #include <cstdio> #include <cmath> #include <cstring> #include <algorithm> #include <math.h> #include <queue> #define INF 0x3f3f3f3f using namespace std; typedef long long ll; const int N = 400; int n,tot;//n为二维平面上点的个数,tot为凸包上点的个数 struct node { int x,y; }a[N],p[N];//p[]用来储存凸包 double dis(node a,node b) { return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y); } double multi(node p0,node p1,node p2) { return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y); } int cmp(node p1,node p2)//极角排序;使用atan2计算会有部分误差,所以使用叉积计算比较精确 { int x=multi(a[0],p1,p2);//叉积大于0,表示向量a在向量b的顺时针方向,叉积小于0,向量a在向量b的逆时针方向,等于0,表示两个向量重合! if(x>0||(x==0&&dis(p1,a[0])<dis(p2,a[0]))) return 1; return 0; } void Graham() { int k=0; for(int i=0;i<n;i++) if(a[i].y<a[k].y||(a[i].y==a[k].y&&a[i].x<a[k].x)) k=i;//得到右下角的那个点的坐标 swap(a[0],a[k]); sort(a+1,a+n,cmp); tot=1,p[0]=a[0],p[1]=a[1]; for(int i=2;i<n;i++) { while(tot && multi(p[tot-1],p[tot],a[i])<=0) tot--; p[++tot]=a[i]; } printf("凸包上的节点:\n"); for(int i = 0;i <= tot;i ++) printf("%d %d\n",p[i].x,p[i].y); } int main() { while(~scanf("%d",&n)) { for(int i = 0;i < n;i ++) scanf("%d%d",&a[i].x,&a[i].y); Graham(); } }
参考博客
叉积含义:https://blog.csdn.net/y990041769/article/details/38258761