Efficient represention for growing circles in 2D space?
想象有一个 2D 空间,在这个空间中,有一些圆圈以不同的恒定速率增长。什么是存储这些圆圈的有效数据结构,以便我可以查询"哪些圆圈在时间
编辑:我确实意识到我可以将圆的初始状态存储在空间数据结构中,并在我与半径为
附加编辑:我可以通过拆分圆圈并按增长率对它们进行分组来进一步增强上述方法,然后将上述方法应用于每个组,但这需要有限的时间才能有效。
将圆表示为 3d 中的圆锥,其中第三维是时间。然后使用 BSP 树尽可能地对它们进行分区。
一般来说,我认为测试交集的最坏情况总是 O(n),其中 n 是圆的数量。大多数空间数据结构通过巧妙地划分空间来工作,以便一小部分对象(希望接近一半)在每一半中。但是,如果对象重叠,则分区不能完美;总会有多个对象在一个分区中的情况。如果你只考虑两个圆重叠的情况,没有办法画一条线,使一个圆完全在一侧,另一个圆完全在另一侧。采取逻辑极端,假设圆的任意定位和任意半径,没有办法对它们进行分区,以便测试相交需要 O(log(n))。
这并不意味着,在实践中,您不会从使用树中获得很大的优势,但您获得的优势将取决于圆的配置和查询的分布。 很抱歉,这还没有完全考虑清楚,但您似乎可以研究乘法加权 Voronoi 图 (MWVD)。似乎对手可能会强迫您使用一系列放置良好的查询来计算一个,所以我觉得它们为您的问题提供了一个下限。 假设您根据输入数据计算 MWVD。然后对于查询,您将返回离查询点"最近"的圆圈。然后,您可以确定该圆圈是否实际包含查询时的查询点。如果没有,那么你就完成了:没有圆圈包含你的观点。如果是这样,那么您应该在没有该生成器的情况下计算 MWVD 并运行相同的查询。您可能能够从旧的计算新的 MWVD:必须填充包含已删除生成器的单元格,并且似乎(尽管我没有证明)唯一可以填充它的生成器是它的邻居. 这是我大约一周前发布的另一个问题的简化版本:
如何找到射线与移动圆的第一个交点
我还没有时间描述预期的解决方案,但我会尝试在这里概述它(对于这个简单的案例)。
解决这个问题的方法是使用动力学 KD-tree。如果您不熟悉 KD 树,最好先阅读它们。您还需要将时间添加为附加坐标(您将空间设为 3d 而不是 2d)。我还没有实现这个想法,但我相信这是正确的方法。
如果您如前所述,在 3d 中用垂直圆锥体表示生长的圆圈,那么您可以将空间划分为规则(可能是六边形)排列的垂直圆柱体网格。对于每个圆柱体,计算与所有圆锥相交的最小和最大高度(时间)。如果圆心(圆锥的顶点)位于圆柱体内,则最短时间为零。然后按最小交叉时间对锥进行排序。作为这种索引的结果,对于每个圆柱体,您将拥有具有 3 个值的有序记录序列:最短时间、最长时间和圈数。
当您检查 3d 空间中的某个点时,您获取它所属的圆柱体并迭代其序列,直到存储的最小时间超过给定点的时间。所有获得的锥体,其最大时间也小于给定时间,保证包含给定点。只有给定时间介于最小和最大交叉时间之间的锥体才需要重新计算。
在索引和运行成本之间存在一个经典的权衡 - 圆柱体直径越小,相交时间范围越小,因此每个点需要重新计算的锥体越少,但需要索引的圆柱体越多。如果圆心分布不均匀,则可能值得搜索比常规网格更好的圆柱放置配置。
附言我在这里的第一个答案 - 刚刚注册发布它。希望不晚。
为了解决少数几个快速增长的圈子的情况,您可以按增长率降序对圈子进行排序,并检查每个增长最快的 k 个圈子。要在给定 t 的情况下找到合适的 k,我认为您可以执行二进制搜索来找到索引 k,使得 k*m = (t * growth rate of k)^2 其中 m 是您需要找到的常数因子通过实验。这将平衡随 k 线性增长的部分与随增长率二次下降的部分。
我认为创建一个解决这个问题的二叉树是可能的。
每个分支都应该包含一个成长的圈子,一个用于分区的静态圈子,以及分区圈干净分区的最晚时间。此外,包含在一个节点中的成长圈应该总是比它的任何一个子节点的成长圈都有更快的成长速度。
要进行查询,请获取根节点。首先查看它的生长圈,如果它包含查询时的查询点,则将其添加到答案集中。然后,如果你查询的时间大于分割线断线的时间,则查询两个子节点,否则如果点在分割圈内,则查询左节点,否则查询右节点。
我还没有完全完成插入的细节,(困难的部分是更新分区圈,使内外节点数大致相等,最大化分区破坏的时间) .
您可以测试它们的边界框以过滤掉不包含该点的圆圈,而不是考虑圆圈。如果您的边界框边都已排序,这实际上是四次二进制搜索。
棘手的部分是在任何给定时间重建已排序的边,
人们会就使用的空间索引类型提出很多建议,但我想提供一些正交建议。
我认为你最好根据时间建立一些索引,即 t_0 < t_1 < t_2 ...
如果一个点在 t_i 与一个圆相交,它也会在 t_{i 1} 与它相交。如果您事先知道该点,则可以消除与 t_i 点相交的所有圆,以便在 t_{i 1} 及以后进行所有计算。
如果您事先不知道该点,那么您可以保留这些时间点树(根据给定时间每个圆的大小构建)。在查询时(例如 t_query),找到满足 t_{i-1} < t_query <= t_i 的 i。如果您在 t_i 检查所有可能的圆圈,您将不会有任何误报。
这是对"时间动态感知"数据结构的一种破解,但我不知道。如果您有一个线程环境,那么您只需要维护一个空间索引并在后台处理下一个。为了能够以低延迟响应查询,这将花费您大量的计算。该解决方案至少应与 O(n) 解决方案进行比较(遍历每个点并检查 dist(point, circle.center) < circle.radius).
你的圈子的中心是如何分布的?如果它们相当均匀地覆盖平面,您可以离散空间和时间,然后执行以下预处理步骤:
1 2 3 4 5 | for (t=0; t < max_t; t++) foreach circle c, with centre and radius (x,y,r) at time t for (int X = x-r; X < x+r; x++) for (int Y = x-r; Y < y+r; y++) circles_at[X][Y][T].push_back (&c) |
(假设您沿整数边界离散空间和时间,当然可以随心所欲地缩放和偏移,您可以稍后添加圆圈或通过推迟计算
那么您在时间 (t) 对点 (x,y) 的查询可以对
进行强力线性检查
权衡是显而易见的,增加三个维度中任何一个维度的分辨率都会增加预处理时间,但会在
某种空间索引,例如四叉树或 BSP,将为您提供 O(log(n)) 访问时间。
例如,四叉树中的每个节点都可以包含一个指向与其相交的所有圆的指针的链表。
顺便问一下,有多少圈?对于小的 n,您也可以只迭代它们。如果您必须不断更新空间索引并跳过所有缓存行,那么暴力破解可能最终会更快。