计算几何 学习笔记

Published on

概述

计算几何主要解决 点、线、面 之间的位置和大小关系。

向量

向量是一个有大小和方向但不确定位置的量,但我们可以用 $\overrightarrow {AB}$ 表示一个从 $A$ 点到 $B$ 点的向量。另外 $\left\lvert AB \right\rvert$ 表示向量的(又称模长,是向量的大小和长度)。

向量加法

对每个维度分别相加,相当于将两个向量首尾相接。

设 $\vec P = \left(a, b\right), \vec Q = \left(c, d\right)$,则 $\vec R = \vec P + \vec Q = \left(a + c, b + d\right)$

向量减法

对每个维度分别相减,相当于被减向量加上减向量的反向量。

向量点乘

点乘符号为 $\cdot$。$\vec P \cdot \vec Q = \left\lvert P \right\rvert \left\lvert Q \right\rvert \cos\theta$,其中 $\theta$ 为 $\vec P$ 与 $\vec Q$ 的夹角。

点乘的结果被称为点积,又称内积,是标量。

设 $\vec P = \left(a, b\right), \vec Q = \left(c, d\right)$,则 $\vec P \cdot \vec Q = a \times c + b \times d$


(此图来自 wikipedia)

点乘满足交换律。

点乘常用于判断向量的前后关系

  • $\vec a \cdot \vec b > 0$:夹角为锐角
  • $\vec a \cdot \vec b = 0$:夹角为直角
  • $\vec a \cdot \vec b < 0$:夹角为钝角

向量叉乘

点乘符号为 $\times$。$\left\lvert\vec P \times \vec Q\right\rvert = \left\lvert P \right\rvert \left\lvert Q \right\rvert \sin\theta$,其中 $\theta$ 为 $\vec P$ 与 $\vec Q$ 的夹角。

点乘的结果被称为叉积,又称外积,是向量,该向量的方向用右手系得出,与坐标平面垂直,与 $Z$ 轴平行


(此图来自 wikipedia)

设 $\vec P = \left(a, b\right), \vec Q = \left(c, d\right)$,则 $\left\lvert\vec P \cdot \vec Q \right\rvert = a \times d - b \times c$

叉乘不满足交换律,交换后得出的结果与原结果互为相反数。

叉乘常用于判断向量的左右关系

  • $\vec a \times \vec b > 0$:$\vec b$ 在 $\vec a$ 的逆时针方向
  • $\vec a \times \vec b = 0$:两向量共线
  • $\vec a \times \vec b < 0$:$\vec b$ 在 $\vec a$ 的顺时针方向

两向量叉积的模长等于两条向量构成的平行四边形的有向面积,因为三角形面积为 $\dfrac12ab\sin\theta$

射线法:点包含问题

射线法用于检测一个点是否在一个多边形内部。
从该点开始向某个方向作射线,检查该射线与多边形每一边的相交情况。如果是奇数说明该点在多边形内部, 还有在边上的情况需要特殊处理。注意判断该射线与某一边共线或交在拐点处的情况。最好的方法就是作一条斜率无穷小的射线,避免出现这些问题。

时间复杂度:$\Theta\left(n\right)$

// 2 : inside, 1 : on seg, 0: outside
tp contain(vector<Point> ps, Point p) {
  tp n = ps.size(), ret = 0;
  for (tp i = 0; i < n; ++i) {
    Point u = ps[i], v = ps[(i + 1) % n];
    if (OnSeg(u, v, p)) return 1;
    if (cmp(u.y, v.y) <= 0) swap(u, v);
    if (cmp(p.y, u.y) > 0 || cmp(p.y, v.y) <= 0) continue;
    ret ^= CrossOp(p, u, v) > 0;
  }
  return ret * 2;
}

旋转法:简单多边形面积

任选一点 $P$,按顺序枚举每多边形上两个相邻的顶点,累计三点围成的有向面积即可。不该算的面积被抵消掉,该算的恰好算了一遍。不好描述,拿个例子玩一玩就明白了。

时间复杂度:$\Theta\left(n\right)$

double area(vector<Point> ps) {
  double ret = 0;
  for (tp i = 0; i < ps.size(); ++i) res += cross(ps[i], ps[(i + 1) % n]);
  return res / 2;
}

二维凸包

定义:能包含所有点的最小凸多边形。
在非严格凸包中可以接受三点共线的情况。

我们的思路是先求上凸壳再求下凸壳,然后并起来。

首先维护一个栈,往里面不停丢点。

假设黑色的是我们当前的凸壳,现在要加入红点。现在红边与最后一条黑边不再构成顺时针旋转关系,于是将最后一个黑点弹出,再次检查。

若依然不满足旋转关系,就继续弹出最后一个黑点,直到满足。

然后把红点加入栈中。

此算法名为 Graham 算法,由于要排序,复杂度为 $\mathcal O\left(n \log n\right)$。

参考

https://en.wikipedia.org/wiki/Dot_product
https://en.wikipedia.org/wiki/Cross_product
https://oi.men.ci/geometry-notes/


No comments

Post a comment