# 一、多边形的三角剖分(Polygon Triangulation)

# 1. 算法 1

row
1
2
3
while 多边形 P 尚未被完全三角剖分:
找到一条有效对角线 (x,y)
输出对角线 (x,y)

# 📌算法复杂度分析:

算法复杂度从以下三个方面分析:

  • 对角线数量(Number of potential diagonals)
    • 一个多边形有 n 个顶点,每对顶点都有可能构成对角线,所以潜在对角线最多为 O (n2) O (n^2) O (n2)。
  • 检查一条潜在的对角线是否合法(Testing one potential diagonal)
    • 对于一条潜在对角线,要检查它是否在多边形内、不与其他边交叉等情况,复杂度为 O (n) O (n) O (n)。
  • 整体复杂度(Time complexity)
    1. 测试一条对角线的有效性: O (n)
    2. 潜在对角线数量: O(n2)O(n^2)
    3. 需要迭代的次数:每次成功找到一条有效对角线,可以把多边形顶点数量减少一个,总共需要 O (n) 次迭代。
      因此,总体复杂度为:

    O(n)×O(n2)×O(n)=O(n4)O(n) \times O(n^2) \times O(n) = O(n^4)

# 2. 算法 2 (耳切法)

row
1
2
3
4
5
当多边形顶点数 n > 3 时,循环执行:
定位一个有效的耳朵尖点 v2
输出耳朵对角线 (v1, v3)
从多边形中删除 v2

简单来说,就是:

  • 每次在多边形上找到一个合适的 “耳朵”。
  • 输出构成该耳朵的对角线。
  • 移除该耳朵尖点(v2),形成一个新的更小的多边形。
  • 重复以上步骤,直到多边形完全被三角化。

# 📌 算法复杂度分析:

  • while 循环的迭代次数
    每次删除一个顶点,总共需要删除 (n - 3) 个顶点,因此迭代次数是 O (n) O (n) O (n)。

  • 每次迭代时寻找有效耳朵的复杂度
    最坏情况下,每次要遍历所有顶点,并检查耳朵是否合法,复杂度为 O (n2) O (n^2) O (n2)。

  • 每次输出对角线和删除顶点的复杂度
    输出对角线和删除顶点都是常数时间,即 O (1) O (1) O (1)。

因此总体复杂度为:

((n3)×O(n2)=O(n3)((n - 3) \times O(n^2) = O(n^3)

# 3. 算法 3(改进的耳切法)

row
1
2
3
4
5
6
7
8
第一步:预处理所有的有效耳朵,构成耳朵集合 S(复杂度 O(n²))

第二步:重复以下步骤直到多边形完全三角化 (n > 3):
- 在集合 S 中快速找到一个有效的耳朵 v2(复杂度 O(1))
- 输出耳朵对角线 (v1,v3)(复杂度 O(1))
- 删除耳朵尖点 v2(复杂度 O(1))
- 更新集合 S:删除和新增受影响的耳朵(复杂度 O(n))

# 📌 算法复杂度分析:

  1. 预处理阶段

    • 计算并存储所有有效的耳朵,最多需要检查所有顶点的组合,复杂度为

    O(n2)O(n^2)

  2. 循环阶段(总共需循环 n-3 次)

    • 查找有效耳朵:利用预先维护的耳朵集合,快速得到一个耳朵,复杂度为 O (1)。
    • 输出对角线、删除耳朵尖点:直接删除顶点,复杂度为 O (1)。
    • 更新耳朵集合:删除旧耳朵、检查和添加新的耳朵,最坏情况下需要遍历一些顶点,复杂度为 O (n)。

循环阶段总复杂度为:

(n3)×O(n)=O(n2)(n - 3) \times O(n) = O(n^2)

最终,总复杂度为:

O(n2)(预处理阶段为O(n2),循环阶段也是O(n2))O(n^2) \quad (\text{预处理阶段为} O(n^2), \text{循环阶段也是} O(n^2))

# 4. 算法 4

算法 4 分为两个主要步骤:

  1. 将多边形 P 划分为若干个 y - 单调多边形(y-monotone pieces)
  2. 对每个 y - 单调多边形进行三角剖分
    #TODO: 添加 y-monotone pieces 图片

# 为什么单调多边形更容易剖分

📍 什么是 y 单调多边形?

  • 一个多边形被称为 y 单调多边形,意味着对于任意一条与 y 轴垂直的水平线,其与多边形相交的部分总是单连通的,也就是说只有一段连续的区间。
    换句话说,从下到上看,一个 y 单调多边形总是可以划分成左链(左侧一系列连续顶点)和右链(右侧一系列连续顶点)两条明显的边链。

📌 为什么 y 单调多边形容易进行三角剖分?

  1. 明确的上下结构
    • 由于 y 单调多边形天然具备上下层次结构,我们可以从最低点向最高点逐一扫描,始终能清楚地知道当前顶点与前面哪些顶点连线可以构成有效的三角形。
  2. 容易的顶点排序
    • 对 y 单调多边形顶点进行 y 坐标排序即可轻松获得扫描次序,这种排序仅需 O (n)。
  3. 更易维护数据结构
    • 在扫描的过程中,使用一个栈结构即可轻松维护潜在的 “对角线连接点”,因此每个顶点只需常数或接近常数次处理操作,总复杂度为 O (n)。

# 二、扫线算法(Sweep Line Algorithm)

# 1. 用扫线算法解决区间深度问题

#TODO 添加区间深度示意图

# 🚩 算法步骤详解:

第一步:

  • 将所有区间的端点从左到右排序(总共 2n 个端点)
    复杂度为 O (nlog⁡n) 因为需要排序。

第二步:初始化

  • currentDepth :当前扫描位置的区间重叠数,初始值为 0。
  • maxDepth :扫描过程中的最大深度,初始值为 0。

第三步:扫描所有端点
从左向右依次扫描所有端点(共 2n2n 个端点),每个点分别是区间的左端点或右端点:

  • 当扫描线经过一个区间左端点时:
    • 表明扫描进入一个新的区间,因此 currentDepth++
    • 如果 currentDepth 超过了 maxDepth ,更新 maxDepth
  • 当扫描到一个右端点时:
    • 表明离开了某个区间,因此 currentDepth--
      循环的总复杂度为 O (n) O (n) O (n),因为只需遍历一遍所有端点(共 2n2n2n 个)。

# 📌 总体复杂度分析:

  • 排序步骤:$$O (n \log n)$$
  • 循环计算:遍历所有端点,共有 2n 个端点,因此为 O (n)。

最终算法复杂度为:

O(nlogn)+O(n)=O(nlogn)O(n \log n) + O(n) = O(n \log n)

# 2. 线段相交问题(Segment Intersection)

# 🌟 什么是 Segment Intersection 问题?

给你平面上 n 条线段,要求:

  • 判断这些线段中是否存在两条相交(Intersection Detection)。
  • 或者报告出所有相交线段对(Intersection Reporting)。

最直接的做法(暴力算法)需要检查每对线段组合,复杂度为 O(n2)O(n^2)


# 🚩 扫描线(Sweep Line)方法介绍:

为了高效地解决上述问题,通常使用扫描线技术

  • 从左到右移动一条垂直的虚拟 “扫描线”。
  • 当扫描线移动时,我们记录并维护与它相交的线段(线段状态)。
  • 在扫描线移动过程中,只需要考虑那些可能相邻的线段是否相交,显著减少检查次数。

# 🎯 扫描线算法具体步骤:

步骤 1:初始化状态

  • 将线段的所有端点按照 x 坐标从左到右排序,作为事件点(Event points)。

步骤 2:维护两个关键数据结构:

  • 事件队列(Event Queue)
    • 存储所有事件(如线段起点、终点),按照 x 坐标排序。
  • 扫描线状态(Sweep line status)
    • 维护当前与扫描线相交的所有线段。
    • 使用二叉搜索树(BST)来实现高效插入和删除,复杂度为 O (log⁡n)

# ⚙️ 算法步骤和复杂度分析(Detection):

  • 每当遇到一个事件(端点):
    • 如果是线段的起点,就加入 BST,并检查它与相邻线段是否相交。
    • 如果是线段终点,就从 BST 删除,并检查新的邻居线段是否相交。

因为每次插入、删除都要做常数次的邻居相交检测,而插入和删除的操作都是 O (log⁡n),所以整体复杂度为:

  • Intersection Detection: $$O(n \log n)$$
  • Intersection Reporting: $$O (n \log n + h \log n)$$,其中 h 为相交线段对数。

# 🖼️ 为什么比暴力方法更高效?

  • 暴力算法:每对线段检查一次相交情况,复杂度为 O (n^2)
  • 扫描线算法:利用状态维护和邻居线段关系,快速定位可能相交的线段,大大提高效率至 O (n log n)(对于检测),以及 O (nlog⁡n+hlog⁡n) 对于报告,其中 h 为相交线段对数)。

# 算法实现

🚩 算法(线段相交检测)核心步骤:

使用扫描线法来判断一组线段是否存在相交:

  1. 数据结构(T):

    • 扫描线从左向右移动,使用平衡二叉搜索树(Balanced Binary Search Tree,简称 BST)来维护扫描线当前所相交线段的顺序。
  2. 删除线段时

    • 当扫描线从左到右经过线段的右端点(结束端点),需要将该线段从树中删除。
    • 此时,被删除线段原本相邻的两条线段在删除后会变成新的相邻线段,因此只需检查这两个线段是否相交。
  3. 插入线段时

    • 当扫描线遇到新的线段(左端点)时,要插入树中。
    • 插入线段时,新线段与树中原有的两个线段变为相邻关系。只需检查新线段与这两个线段是否相交即可。
  4. 发现相交时

    • 如果发现任何相交,立即停止,可以报告存在相交情况。

📌 时间复杂度分析:

扫描线算法中事件总数最多为 2n(每条线段有两个端点),处理每个事件包括:

  • 在树 T 中插入或删除一条线段需要 O (log⁡n) 时间。
  • 每次插入或删除操作都会检查最多两个邻居线段,因此每次操作的复杂度也是 O (log⁡n)

因此,总体复杂度:

  • 每个端点处理一次,总共 2n 个端点。
  • 每次处理复杂度为 O (log⁡n)
  • 整体复杂度:O (nlog⁡n)

# 三、平面扫描解决三角剖分问题

# 🚩 第一步:划分成 y 单调多边形

先将复杂的多边形划分为更易处理的 y 单调多边形:

  • 定义
    • 一个 y 单调多边形(y-monotone polygon)是指任意一条水平线(垂直于 y 轴)与该多边形相交的部分始终是单连通的(连续的)。
# 划分方法(Plane Sweep 方法):
  • 从上向下(或从下向上)移动扫描线,按 y 坐标顺序扫描多边形的所有顶点。
  • 在扫描过程中识别不同的顶点类型:
    • 起始顶点(start vertex)
    • 终止顶点(end vertex)
    • 分裂顶点(split vertex)
    • 合并顶点(merge vertex)
  • 当遇到导致 y 单调性被破坏的 “分裂顶点” 和 “合并顶点” 时,增加适当的对角线,从而将多边形划分成一系列的 y 单调多边形。
  • 扫描线的状态用平衡二叉搜索树维护,以确保高效地找到每个顶点的邻接关系,复杂度为 O(n log n)

# 🚩 第二步:y 单调多边形的三角剖分过程(具体步骤)

  1. 对 y 单调多边形的所有顶点按照 y 坐标从下往上排序。
  2. 依次遍历排序后的顶点序列,利用一个栈:
    • 如果新顶点与栈顶顶点属于不同链,则连接栈中所有顶点(除最后一个)形成新三角形,然后更新栈。
    • 如果新顶点和栈顶元素位于同一链上,则弹出栈顶元素,并依次检查和弹出能够和当前顶点构成有效对角线的顶点,加入新的对角线。
  3. 重复直到所有顶点处理完成,即完成三角剖分。