计算几何学习笔记(一)
参考文献:计算几何算法与应用(第三版)
1、前言
1.1 点积:
A
⃗
B
⃗
=
∣
A
⃗
∣
∣
B
⃗
∣
c
o
s
<
A
⃗
,
B
⃗
>
=
x
a
y
a
+
x
b
y
b
\vec{A}\vec{B}=|\vec{A}||\vec{B}|cos<\vec{A},\vec{B}>=x_ay_a+x_by_b
AB=∣A∣∣B∣cos<A,B>=xaya+xbyb
几何意义:A在B所在方向的投影的模长和B的模长的乘积,可判断AB是否垂直
1.2 叉积:
A
⃗
×
B
⃗
=
∣
A
⃗
∣
∣
B
⃗
∣
s
i
n
<
A
⃗
,
B
⃗
>
=
x
a
y
b
−
x
b
y
a
\vec{A}×\vec{B}=|\vec{A}||\vec{B}|sin<\vec{A},\vec{B}>=x_ay_b-x_by_a
A×B=∣A∣∣B∣sin<A,B>=xayb−xbya
几何意义:AB为边构成的平行四边形的有向面积,判断A、B方向,是否共线:
a×b>0——逆时针;
a×b=0——共线;
a×b<0——顺时针。
例如:相对于由p和q两点确定的一条有向直线, 某点r究竟是位于其左侧还是右侧。 令p = (px, py),q = (qx, qy),r = (rx, ry)。可根据叉积 p q ⃗ × p r ⃗ \vec{pq}×\vec{pr} pq×pr也即 ( D = ∣ 1 p x p y 1 q x q y 1 r x r y ∣ ) \bigl (D=\begin{vmatrix} 1&px& py\\ 1&qx & qy\\ 1&rx&ry \end{vmatrix}\bigr) (D=∣∣∣∣∣∣111pxqxrxpyqyry∣∣∣∣∣∣)
若D>0,逆时针方向,r点在左侧;
若D=0,三点共线;
若D<0,顺时针方向,r点在右侧;
1.3 线段AB、CD的交点P
以直线AB为准,根据叉积判断,若C、D两点在线段两侧,则存在交点P。之后根据交点P是否在线段上,分情况求出交点坐标。
- 判断是否相交:若 A B ⃗ × C D ⃗ = 0 \vec{AB}×\vec{CD}=0 AB×CD=0则线段不相交
- 交点是否在线段上
(1)若 ( A B ⃗ × A C ⃗ ) ⋅ ( A B ⃗ × A D ⃗ ) > 0 (\vec{AB}×\vec{AC})\cdot(\vec{AB}×\vec{AD})>0 (AB×AC)⋅(AB×AD)>0则交点不在线段上;
(2)若 ( A B ⃗ × A C ⃗ ) ⋅ ( A B ⃗ × A D ⃗ ) ≤ 0 (\vec{AB}×\vec{AC})\cdot(\vec{AB}×\vec{AD})\leq0 (AB×AC)⋅(AB×AD)≤0则交点在线段上(为0说明交点为某端点)。 - 求交点坐标
(1)交点不在线段上:
P ⃗ = A ⃗ + 1 ( 1 − α ) A ⃗ B ⃗ \vec{P} =\vec{A}+\frac{1}{(1- \alpha) }\vec{A}\vec{B} P=A+(1−α)1AB
其中 α = A B ⃗ × A D ⃗ A B ⃗ × A C ⃗ \alpha=\frac{\vec{AB}×\vec{AD}}{\vec{AB}×\vec{AC}} α=AB×ACAB×AD
(2)交点在线段上:
P ⃗ = A ⃗ + 1 ( 1 + α ) A B ⃗ \vec{P} =\vec{A}+\frac{1}{(1+ \alpha) }\vec{AB} P=A+(1+α)1AB
其中 α = C D ⃗ × C B ⃗ C D ⃗ × C A ⃗ \alpha=\frac{\vec{CD}\times\vec{CB}}{\vec{CD}\times\vec{CA}} α=CD×CACD×CB
2、凸包
2.1 问题分析
面对具有几何本质的算法问题,我们所采用的解决方法大多需要具备两方面要素:一是对该问题的几何特性的深刻理解,二是算法和数据结构的合理应用。
平面凸包的计算是一个比较常见的平面几何问题。若
∃
S
\exists S
∃S,
∀
p
,
q
∈
S
\forall p ,q\in S
∀p,q∈S,有线段
p
q
‾
∈
S
\overline{pq}\in S
pq∈S则称S为凸集。
问题描述:给定平面点集 P = { p1, …, pn },通过计算从 P 中选出若干点,它们沿顺时针方向依次对应于 CH( P)的各个顶点。
input = 平面上一组点:p1, p2, p3, p4, p5, p6, p7, p8, p9
output = 凸包的表示:p4, p5, p8, p2, p9
对于CH( P)边界上任一边所在的直线,P中所有的点均居于同侧。为降低时间复杂度,我们采取递增式策略,构建一个递增式算法,逐步引入P中各点,每引入一个点都更新目前的解,直至遍历所有点。
具体方案:沿顺时针方向,从左至右找出上方边界各点构成上凸包;然后自右向左进行扫描找出下方边界各点构成下凸包。
判断条件:引入新点时保证每次都是右拐,否则剔除中间点,直至满足条件或只剩两个点。
2.2 具体实现
计算凸包算法伪代码:
/************************************
> 算法 CONVEXHULL( P)
> 输入:平面点集P
> 输出:由CH(P )的所有顶点沿顺时针方向组成的一个列表
1. 根据x-坐标,对所有点进行排序,得到序列p1, …, pn
2. 在Lupper中加入p1和p2(p1在前)
3. for (i← 3 to n)
4. do 在Lupper中加入pi
5. while (Lupper中至少还有三个点,而且最末尾的三个点所构成的不是一个右拐)
6. do 将倒数第二个顶点从Lupper中删去
7. 在Llower中加入pn和pn-1(pn在前)
8. for (i← n-2 downto 1)
9. do 在Llower中加入pi
10. while (Llower中至少还有三个点,而且最末尾的三个点所构成的不是一个右拐)
11. do 将倒数第二个顶点从Llower中删去
12. 将第一个和最后一个点从Llower中删去 (以免在上凸包与下凸包联接之后,出现重复顶点)
13. 将Llower联接到Lupper后面(将由此得到的列表记为L)
14. return(L)
***********************/
整个算法的时间复杂度取决于第一步的排序即O(nlogn)。因此,给定包含 n 个点的任意一个平面点集,其凸包都可以在 O(nlogn)时间内构造出来。
退化情况:当出现三点共线时,要保证严格右拐,将共线视为左拐舍弃。
bool Judge_right_turn(POINT &p1, POINT &p2, POINT &p3)
{
int flag1 = (p2.x - p1.x)*(p3.y - p1.y) - (p3.x - p1.x)*(p2.y - p1.y); //求向量×积
if (flag1 >= 0)
return false;
return true;
}
本文使用C++的vector容器实现如下:
using pt_List = vector < POINT >;
pt_List CONVEXHULL(pt_List &list0)
{
pt_List upper;
pt_List lower;
int list0_num = list0.size();
//排序
sort(list0.begin(), list0.end(), Compare_point);
//先求上凸包
upper.push_back(list0.at(0)); //索引添加元素
upper.push_back(list0.at(1));
//递增策略,逐个添加点pi
for (int i = 2; i < list0_num;i++)
{
POINT pi = list0[i];
while (true)
{
auto iter = upper.end();
--iter;
POINT p2 = *iter;
--iter;
POINT p1 = *iter;
//判断pi是否构成右拐,是添加pi,否删除中间点,继续判断
if (Judge_right_turn(p1, p2, pi))
{
upper.push_back(pi);
break;
}
else
{
upper.pop_back();
if (upper.size() == 1)
{
upper.push_back(pi);
break;
}
}
}
}
//求下凸包
lower.push_back(list0.at(list0_num-1));
lower.push_back(list0.at(list0_num - 2));
for (int i = list0_num - 2; i > 0;i--)
{
POINT pi = list0[i-1];
while (true)
{
auto iter = lower.end();
--iter;
POINT p2 = *iter;
--iter;
POINT p1 = *iter;
//判断pi是否构成右拐,是添加pi,否删除中间点,继续判断
if (Judge_right_turn(p1, p2, pi))
{
lower.push_back(pi);
break;
}
else
{
lower.pop_back();
if (lower.size() == 1)
{
lower.push_back(pi);
break;
}
}
}
}
//连接上下凸包
upper.pop_back();
lower.pop_back();
upper.insert(end(upper), begin(lower), end(lower));
return upper;
}