三角剖分
对于二维平面上一个点集 \(V\) ,设其凸包为 \(CH(V)\) ,构造一个边集 \(E\) ,那么点集 \(V\) 的三角剖分 \(T(V)=G=(V,E)\)满足以下条件:
- 是一个平面图,即除了端点,所有边之间没有其他交点。
- 该平面图的任意一个面都是三角面。
- \(CH(V)\) 的边都在 \(E\) 中
##Delaunay 三角剖分
对于一个三角剖分 \(T\) ,若对于其中任意一个三角面,其外接圆内部不包含除三角面端点外 \(V\) 中其他点,则称之为 Delaunay 三角剖分,简称 \(DT\) 。下文中我们所说的三角剖分都指 Delaunay 三角剖分。
分治法求 DT
考虑若有两个点集 \(V_1\) 和 \(V_2\) ,满足 \(V_1\) 的凸包 \(CH_1\) 和 \(V_2\) 的凸包 \(CH_2\) 没有交点,设分别在左侧和右侧,若我们已经得到了它们的三角剖分 \(DT_1\) 和 \(DT_2\) ,我们应该怎样求出点集 \(V=V_1\cup V_2\) 的三角剖分 \(DT\) ?
首先找到 \(CH_1\) 和 \(CH_2\) 的下公切线,这显然是 \(V\) 的凸包 \(CH\) 中的一条边,所以它必然在 \(DT\) 中,找的方法很简单,任选一个 \(V_1\) 中的点 \(P_1\) 和 \(V_2\) 中的点 \(P_2\) ,枚举 \(DT_1\) 中与 \(P_1\) 中相连的点,若在 \(P_1P_2\) 的右侧则更新 \(P_1\) ,\(P_2\) 同理。这样复杂度是线性的。
然后我们需要从下往上慢慢加边,设当前的 \(Base\) 边所连的左右两个点是 \(P_1\) 和 \(P_2\) ,\(Base\) 边初始是上面求的公切线。我们需要找到 \(P_1\) 和 \(P_2\) 所连的点中,在 \(P_1P_2\) 上方(即向量左侧)且与 \(P_1,P_2\) 的外接圆不包含其他点的那个点。
注意到一个事实,固定两个点 \(A\) 和 \(B\) ,对于任意两个不与 \(AB\) 共线且在 \(AB\) 同侧的点 \(C\) 和 \(D\) ,则必有三角形 \(ABC\) 外接圆包含 \(D\) 或三角形 \(ABD\) 外接圆包含 \(C\) 。定义 \(C\) 小于等于 \(D\) 为 \(ABC\) 的外接圆包含 \(D\) ,\(C\) 等于 \(D\) 为 \(A,B,C,D\) 四点共圆,那么必然有 \(C\le D\) 或 \(D\le C\) 。且注意到若 \(C\le D\) ,则在 \(C,D\) 所在 \(AB\) 这一侧,\(ABC\) 外接圆包含 \(ABD\) 外接圆,故若有 \(C\le D,D\le E\) ,必有 \(C\le E\) 。那么,这个关系显然是一个全序关系。(注意:这里的包含指在内部或边界上)
所以,我们只需要枚举 \(P_1\) 和 \(P_2\) 所连的所有边,对于在 \(\overrightarrow{P_1P_2}\) 左侧的所有点,找到最大的那个点即可。设这个点为 \(Q\) ,若其在左侧,则需要连边 \(QP_2\) ,并且更新 \(Base\) 边,但这会带来一个问题,新加的这条边可能和原来的边相交,但可能相交的边只可能是原来就与 \(P_1\) 相连的边,枚举删除一下即可,若 \(Q\) 在右侧同理。这一部分复杂度是均摊线性的。
我们将所有点排序,用分治的思想来不断合并 \(DT\) ,总复杂度为 \(O(n\log n)\)。
代码解读
首先要说明一下存边的方式,由于有删边操作,所以最好用链表来维护。
1 | struct Edge{ |
其中 mate
指向这条边的另一半。
连边操作为:
1 | void addEdge(int u, int v){ |
分治主体:
1 | void work(int l, int r){ |
找两个凸包的下公切线:
1 | bool flag = 0; |
连边和删边:
1 | addEdge(i, j); |
接下来是一些计算几何部分细节的说明。
1 | const double eps = 1e-10; |
OnLeft
用来判断点 \(C\) 在 \(AB\) 的哪一侧
1 | int OnLeft(Point A, Point B, Point C){ |
InCircle
判断点 \(P\) 是否在 \(ABC\) 外接圆严格内部
1 | double det3(vector<double> p){ |
SegmentProperIntersection
判断线段严格相交
1 | bool SegmentProperIntersection(Point a1 , Point a2 , Point b1 , Point b2) { |
结语
想好怎么实现之后,代码写起来其实并不复杂,整个过程也没有太多的细节。
在求出 \(DT\) 之后,对偶一下即可得到维诺图(Voronoi Diagram)。若想得到每个三角面,使用最左转线法即可,具体可参考平面图转对偶图,在求维诺图的过程中也需要用到。