Voronoi图(一):塞尔达背后的计算几何

1. 林克的难题

玩过塞尔达传说:荒野之息的朋友,应该都会赞叹海拉鲁风景的美丽。我们的林克时不时一人独自在战后废墟中探索一座座神庙,有时武器库告急,也会偶尔探望一下亲爱的人马们。现在林克一个人游荡在海拉鲁大地上,遇到了一个难题:他突然想回到河畔驿站修整一下,恢复心心,但是他究竟需要传送哪个传送塔,才能离驿站最近呢?下图给出了河畔驿站的位置,以及三个比较近的传送塔:平原之塔,双子山之塔和初始之塔:

在这里插入图片描述

为了简化问题,这里我们限制传送点只能是传送塔,神庙等的传送点无效;“离驿站最近”是指传送塔距离驿站的直线距离,当然实际赶路可能不能走直线,不过可以用风弹逃课啦哒~

那么,林克究竟应该选择传送到哪个传送塔呢?我们能用什么方法帮助林克解决这个难题呢?其实这个问题可以转换成计算几何中的Voronoi Diagrams(Voronoi图)问题,只要使用传送塔作为输入点,并计算相应的Voronoi图,我们就能得到想要的答案!

2. 巧用Voronoi图

针对这个问题,可能有童鞋会说直接比较驿站到传送点的直线距离,距离最短就是我们想要的结果。通过上面的图例,我们一眼就能知道双子山之塔最近。但是这个方法的效率如何呢?假设我们有n个传送点,那计算所有距离我们需要O(n)的时间,接近线性时间,似乎好像还挺不错的嘛。注意这里是查询一次的效率,假设我们查询了k次,那么总的时间为:O( n ) * k;如果k = n,那么总的时间为:O( n ^ 2 ),查询效率将会大大地降低。

那有没有更加高效的查询方法呢?或者说,我们是否能先建立一个高效的查询数据结构,让我们每次查询效率更高,即使查询次数够多,也能保证查询的整体效率。其实这就是Offline Algorithm 和 Online Algorithm 的区别,这点我们在 线段树(二):RMQ 也提到过呢。

这里我们会把传送塔看成输入点,对点集计算对应的Voronoi图,然后结合点定位算法,就能高效进行空间点的查询。接下来,我们就来看看如何巧用Voronoi图的性质来解决林克的难题吧。

首先,我们用传送塔作为输入点,计算相应的Voronoi图:
在这里插入图片描述
大家可以看到,被蓝线围成的一块块区域,就是Voronoi Cell,每个Cell都会包含一个输入点(传送点),我们也称输入点为SIte。在得到Voronoi图之后,我们也得到了一个非常有用的性质:

每个Cell里面的任一点,到这个Cell包含的SIte的直线距离是最短的。

运用这个性质,我们来看看之前三个候选的传送塔所在的Cell,以及湖畔驿站所在的Cell:
在这里插入图片描述
我们可以看到,只有双子山之塔和河畔驿站处在同一个Cell里面,平原之塔和初始之塔分别在两个不同的Cell里面,所以我们应该让林克传送到双子山之塔,距离河畔驿站最近:
在这里插入图片描述

有了Voronoi图,我们无论进行多少次查询,每次查询的时间都是:O( logn )。建立Voronoi图和与之对应的梯形图(Trapezoidal Map)需要O( nlogn ),假如一共查询了k(k = n)次,整体的时间复杂度为:

2 * O( n logn ) + k * O( logn ) = 2 * O( n logn ) + n * O( logn ) = O( n logn );

现在时间复杂度比之前的O( n ^ 2 )好了不少,从这里大家也能感受到Voronoi图对空间查询效率的提升。好啦,我们帮助林克解决了难度,他也能继续在海拉鲁的冒险,最后拯救塞尔达公主!那接下来我们就来看看Voronoi图一些重要的性质,以及讲解搭建Voronoi图的算法。

3. 个人项目招募

号外哒,号外哒~ 现在想做一个以Voronoi图算法的塞尔达野炊网页项目哒。想法就是用上面提到的算法去做一个演示网站:比如用户随意输入一个目标点,网页能呈现Voronoi图和相应最近的传送点(包括神庙,神兽等等)。具体的效果交互还没有想好哒,正在构思中,也在倒腾个人网站的搭建~ 如果你对该项目有兴趣,长期招募下面的小伙伴哒:

  1. 开发人员(主要是Web前后端,项目主要算法我已经实现了,直接当API调用就行。但使用的技术越新越好,比如不兼容任何旧版浏览器);
  2. UI设计(比如网页外观如何设计才能即美观又简约哦);
  3. 产品人员(比如如何交互和什么功能才能让用户感受Voronoi图的作用);
  4. 塞尔达资深玩家(对塞尔达野炊,包括今年待发售续作无所不知,可以对项目提出相关建议,比如添加其他功能,比如人马,素材或任务位置查询等等);

有兴趣的童鞋可以联系我的邮箱:402951678@qq.com 或 fengkeyleaf@gmail.com;水平不限哒,敢于挑战就行啦哒,网页开发笔者现在也在自学哒,也是一窍不通哦~


上一节:点定位(五):处理退化情况·续

下一节:Voronoi图(二):基本概念和性质

系列汇总:塞尔达和计算几何 | Voronoi图详解文章汇总(含代码)

4. 参考资料

  1. 塞尔达原地图素材来自NS之家

5. 免责声明

※ 本文之中如有错误和不准确的地方,欢迎大家指正哒~
※ 此项目仅用于学习交流,请不要用于任何形式的商用用途,谢谢呢;


在这里插入图片描述

### Voronoi 的概念 Voronoi 种几何结构,用于描述组离散点之间的空间划分关系。具体来说,给定平面上的组点 \( P \),每个点 \( p_i \in P \) 都对应个区域 \( R(p_i) \),该区域内所有的点到 \( p_i \) 的距离小于等于它们到其他任何点的距离[^1]。 这种划分方式使得平面被分割成多个多边形区域,每个区域都由最近邻的个特定点定义。因此,Voronoi 可以用来解决许多实际问题,比如设施选址、网络覆盖分析以及计算机形学中的纹理生成等应用[^2]。 --- ### 实现算法概述 构建 Voronoi 的经典方法之是 Fortune's Algorithm,这是种基于扫描线的高效算法。以下是其核心原理: - **事件队列**:维护两个主要的数据结构——个是存储圆弧交点的优先级队列(称为“海滩线”),另个是用来记录新出现的关键事件(如站点事件或圆事件)。 - **扫描过程**:通过条水平的虚拟扫描线自上而下遍历整个输入点集,在此过程中动态更新当前形成的 Voronoi 边界。 Fortune’s 算法的时间复杂度为 \( O(n \log n) \),这使其成为处理大规模数据的有效工具。 下面是个简单的 Python 示例来展示如何利用现有的库 `scipy` 来计算并绘制二维空间上的 Voronoi : ```python import numpy as np from scipy.spatial import Voronoi, voronoi_plot_2d import matplotlib.pyplot as plt # 定义随机点集合 points = np.random.rand(30, 2) # 计算 Voronoi vor = Voronoi(points) # 绘制结果 fig = voronoi_plot_2d(vor) plt.show() ``` 上述代码片段展示了如何借助第三方科学计算包快速生成可视化效果良好的 Voronoi 结果。 --- ### 解释性注解的重要性 对于像上面提到的 Fortunes’ Algorithm 这样的复杂实现而言,在关键逻辑处添加清晰易懂的解释型注释尤为重要。例如,在管理事件队列或者调整半径大小时应注明每步操作的目的及其背后的理论依据。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值