计算几何中的点定位(Point Location in Computational Geometry)
好的,我们来讲讲计算几何中的一个基础且重要的问题:点定位。顾名思义,就是给定一个平面细分(比如一张地图)和一个查询点,要快速确定这个点落在哪个区域里。我们从最直观的想法开始,逐步深入到高效的解决方案。
第一步:问题定义与朴素方法
想象你有一张地图,地图被划分成了不同的国家、省份(这些是多边形区域)。现在我给你一个点的经纬度坐标,问你:“这个点在哪个国家?” 这就是点定位问题。
更形式化地说:
- 输入:
- 一个平面的细分 (Planar Subdivision)。这是由一组线段(或直线、曲线)将平面分割成多个互不相交的面 (Faces),每个面是一个多边形区域。例如,多边形的三角剖分、Voronoi图、或者一张简单的地图。
- 一个查询点 q = (x, y)。
- 输出:q 所在的那个面。
最朴素的方法是射线法 (Ray Shooting)。从查询点 q 向右(或任意方向)发出一条水平射线,然后计算这条射线与细分中所有线段的交点。根据计算几何中的奇偶规则,如果交点个数是奇数,则 q 在多边形内部;是偶数,则在外部。通过判断射线穿过了哪些多边形的边界,可以最终定位到 q 所在的面。
但这个方法效率很低。对于一个有 n 条边的细分,每次查询都需要 O(n) 的时间去检查所有边。如果有很多查询点,总时间会非常慢。我们的目标是预处理这个细分结构,使得后续的每次查询都能非常快。
第二步: slabs 法与数据结构的需求
一个自然的优化思路是“分区治理”。想象在平面上所有顶点的 x 坐标处画垂直线,把平面分割成许多竖直的条带,称为 slabs。每个 slab 内部,没有顶点穿过,因此穿过 slab 的边(线段)从左到右的顺序是固定的,并且这些边不会在 slab 内部相交。
查询过程:
- 首先,在 O(log n) 时间内(通过对所有顶点的 x 坐标进行二分查找),找到查询点 q 位于哪两个相邻的垂直线之间,即确定它所在的 slab。
- 然后,在这个 slab 内部,所有边都是从上到下(或从下到上)有序排列的。我们只需要在这些边中进行一次二分查找(O(log n) 时间),就能确定 q 位于哪两条边之间,从而确定它所在的面。
这已经将单次查询时间优化到了 O(log n)。但是,预处理代价很高。我们需要为每个 slab 存储其内部有序的边列表。由于有 O(n) 个 slabs(每个顶点对应一个),而每个边可能出现在多个 slabs 中,总存储空间可能高达 O(n²)。
因此,我们需要一种更聪明、存储空间更少的数据结构来组织 slab 之间的信息。
第三步:梯形图(Trapezoidal Map)与随机增量构造
为了解决 slabs 法的空间爆炸问题,我们引入梯形图。它不是用垂直线,而是从每条线段的端点向上、向下引垂线,直到碰到另一条边或无穷远。这样,整个平面被划分为许多梯形(允许退化为三角形)。
梯形图是 slabs 法的一个“精炼”,每个 slab 被进一步细分。它的关键性质是:梯形图是平面的直线图,其顶点是原线段的端点以及垂线与边的交点。可以证明,对于一个有 n 条线段(无交叉)的细分,其梯形图的大小(梯形数量)是 O(n)。
我们可以用随机增量算法在线性期望时间内构造梯形图及其对应的查询数据结构。基本思想是:
- 随机打乱所有线段的顺序。
- 从空结构开始,一次插入一条线段。
- 插入线段时,找到新线段穿过的所有现有梯形,并将它们切割成新的小梯形。
- 在构造的同时,维护一个有向无环图作为查询结构:每个叶子节点对应一个最终的梯形,每个内部节点是一个判断测试(例如“点在给定直线的左边还是右边?” 或 “点在某条线段的上方还是下方?”)。
查询时,从根节点开始,根据测试结果走左子树或右子树,最终到达一个叶子节点,这个叶子节点就是包含查询点 q 的梯形。由于梯形图的大小是 O(n),这个搜索树的高度期望是 O(log n)。因此,查询时间为 O(log n),期望预处理时间和空间为 O(n)。这是一个非常实用的算法。
第四步:最优最坏情况复杂度与持久化数据结构
梯形图法在期望意义下很好,但我们有时需要最坏情况下的性能保证。已知的点定位问题理论下限是:预处理空间 O(n),查询时间 O(log n)。如何达到这个下限?
一种经典方法是使用持久化搜索树。回想我们最初的 slabs 法:每个 slab 内有一个边的有序列表,我们在这个列表上二分查找。这本质上是在每个 slab 内维护一个搜索结构。
关键观察是:相邻的 slab 非常相似,它们的边列表只相差很少的边(因为只有穿过 slab 边界的边才会进入或离开列表)。持久化数据结构(特别是持久化的平衡二叉搜索树)允许我们高效地存储一个数据结构的多个版本,并且新版本可以共享旧版本的大部分结构。
具体步骤:
- 将所有 slabs 从左到右扫描。
- 为最左边的 slab 构造一个平衡二叉搜索树,其关键字是 slab 内边的 y 轴次序。
- 当从当前 slab 移动到下一个相邻 slab 时,只有那些端点落在两个 slabs 之间的边会被加入或删除。用持久化技术,在 O(log n) 时间内,我们可以从上一个 slab 的搜索树“派生”出下一个 slab 的搜索树,而只创建 O(log n) 个新节点。
- 最终,我们得到 O(n) 个版本的搜索树,总存储空间为 O(n log n)(实际上通过更精巧的技术可以达到 O(n))。
- 对于一个查询点 q,首先通过二分查找确定其所在 slab(O(log n) 时间),然后在该 slab 对应的持久化搜索树中进行一次二叉查找(O(log n) 时间),即可定位。总查询时间 O(log n),预处理空间 O(n)(最优)。
总结
点定位问题展示了计算几何中“空间换时间”和“数据结构设计”的核心思想:
- 问题:在平面细分中快速定位查询点。
- 朴素方法(射线法):O(n) 查询时间,无预处理。
- 改进思路(slabs 法):通过垂直分区,将查询分解为两次二分查找(O(log n)),但导致 O(n²) 的预处理空间。
- 实用算法(梯形图 + 随机增量构造):将平面细分为 O(n) 个梯形,并同时构建一个决策 DAG。达到 期望 O(n) 预处理,O(log n) 查询,易于实现。
- 理论最优(持久化搜索树):利用 slabs 间的相似性和持久化数据结构,达到 最坏情况 O(n) 预处理空间,O(log n) 查询时间,匹配理论下限。
点定位是许多地理信息系统、计算机图形学(如光线追踪)、和网格处理算法的基础操作。