开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第17天,点击查看活动详情
(1)多边形面积
我们利用的是叉乘的几何意义是两向量构成的平行四边形的面积。(除以2就是三角形的面积) 通过与第一个点来将n边形分割成n-2个三角形的和,而每一个三角形面积都可以直接由向量叉乘求得。如下图
我们按照逆时针顺序来求,那么角度都是正的,a 叉乘 b ,结果的正负是由a到b这个的角度的sin决定的。
(2)凸边形(判断)
如果把多边形的边顺序按同一侧作为起点来看,可以发现,凸多边形的边都按同一角度选择着 根据这一规律,我们对边的向量进行叉乘,便可判断 叉乘的几何意义是 一个垂直于两个向量的方向, 如果所有边依次相乘的方向都是一样的, 就是凸多边形, 此题因为规定了必须逆时针, 所以所有相乘都必须是正的(这个可以自己试一下就知道)
(3)点是否在多边形内部
如果是判断在凸边形内部的话,可以依据上面的规律,判断该点依次分别与两个点的叉乘都是正的也就是在左边(逆时针) 在计算几何中,判定点是否在多边形内,是个非常有趣的问题。通常有两种方法: 1.Crossing Number(交叉数) 它计算从点P开始的射线穿过多边形边界的次数。当“交叉数”是偶数时,点在外面;当它是奇数时,点在里面。这种方法有时被称为“奇-偶”检验。 2.Winding Number(环绕数) 它计算多边形绕着点P旋转的次数。只有当“圈数”wn = 0时,点才在外面; 否则,点在里面。 详细解读:blog.csdn.net/u013279723/…
(4)求凸包
首先讲解一下凸包的概念 用比较抽象的说就是: 在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。X的凸包可以用X内所有点 (X1,...Xn)的凸组合来构造. 简单来说: 给你一个点集Q,你可以把Q中的每个点想象成一块木板上的铁钉,而点集Q的凸包就是包围了所有铁钉的一条拉紧 了橡皮绳所构成的形状;
算法:安德鲁算法(Andrew)
在这之前我们要明白一个凸包的重要性质:在这些点集中,x最小,最大,y最小最大,这四个极点,肯定是在凸包上面的,x最小和最大,可以将凸包分为上凸包和下凸包,y最小与最大可以将凸包分为左凸包和右凸包。这里我们是按照左凸包和右凸包来求的。 因此在求之前需要对点进行排序,y小的放前边,y相同的话,x小的放前边。
bool operator <(const point &c)const {
if (y < c.y)
return true;
if (y > c.y)
return false;
if (x < c.x)
return true;
return false;
}
我们先从y最小开始求出右凸包,什么判断点是在凸包上呢,利用叉乘,新的点应该与最近的两个维护的凸点的叉乘不能小于0(也就是三个点)
因此这就为什么说如果我们最后的结果要包含边上的话就是< , 因为如果是边界的话是0(平行),我们不应该排除掉它
依次我们在求到y最大的点的时候,就维护好了右凸边,然后我们再从y最高到y最小来维护出左凸边,因为最后起点会重复加入,减去1即可得到答案。
// 求凸包,返回的是凸包的顶点数 ,ch 中存的就是对应的顶点
int ConvexHull(point *p, int n, point *ch) {
sort(p, p + n);
int m = 0;
for (int i = 0 ; i < n ; ++i) {
while (m > 1 && sign(cross(ch[m - 1] - ch[m - 2], p[i] - ch[m - 2]) ) < 0)
m --;
ch[m++] = p[i];
}
int k = m;
for (int i = n - 2 ; i >= 0 ; --i) {
// p[i] - ch[m - 2] 是 p[i] - ch[m - 1] 也可以!
while (m > k && sign(cross(ch[m - 1] - ch[m - 2], p[i] - ch[m - 2]) ) < 0)
m--;
ch[m++] = p[i];
}
if (n > 1)
--m;
return m;
}
(5)求凸包的直径(平面最远点对)
前置知识: 1、被一对卡壳正好卡住的对应点对称为对踵点,对锺点的具体定义不好说,不过从图上还是比较好理解的。 2、可以证明对踵点的个数不超过3N/2个 也就是说对踵点的个数是O(N)的
3、我们需要对点集先求出凸包的点集(这里不要边上的点)
如果qa,qb是凸包上最远两点,必然可以分别过qa,qb画出一对平行线。通过旋转这对平行线,我们可以让它和凸包上的一条边重合,如图中蓝色直线,因此依据这个我们就可以快速求出对踵点 ,与对踵点与边重合的两个点构成两个向量叉乘的结果< 0 这样才会有两条平行线不经过多边形内部将它们卡住
可以注意到,qa是凸包上离p和qb所在直线最远的点。于是我们的思路就是枚举凸包上的所有边,对每一条边找出凸包上离该边最远的顶点,计算这个顶点到该边两个端点的距离,并记录最大的值。 直观上这是一个O(n2)的算法,和直接枚举任意两个顶点一样了。 然而我们可以发现 凸包上的点依次与对应边产生的距离成单峰函数(如下图:)
根据这个凸包的特性,我们注意到逆时针枚举边的时候,最远点的变化也是逆时针的,这样就可以不用从头计算最远点,而可以紧接着上一次的最远点继续计算。于是我们得到了O(n)的算法。这就是所谓的“旋转”吧! 利用旋转卡壳,我们可以在O(n)的时间内得到凸包的对锺点中的长度最长的点对。
又由于最远点对必然属于对踵点对集合 ,那么我们先求出凸包 然后求出对踵点对集合 然后选出距离最大的即可
double RotaingCalipers(point *ch, int m) {
if (m == 1)
return 0.0;
else if (m == 2)
return dis2(ch[0], ch[1]);
double ret = 0.0;
//j就是对踵点
for (int i = 0, j = 1; i < m ; ++i) {
while (cross(ch[i + 1] - ch[i], ch[j] - ch[i]) < cross(ch[i + 1] - ch[i], ch[j + 1] - ch[i]))
j = (j + 1) % m;
ret = max(ret, get_length(ch[j] - ch[i]));
}
return ret;
}
(6)三角形
在C语言中 sin 的参数是 弧度,而不是角度. 所有我们计算一个角度的sin 值时,应先转成弧度值. 弧度 = 角度 * 3.1415926 / 180.0 同时为了确保精度,PI要详细一些:#define PI 3.1415926535897