查找矩形内所有点的快速算法
Fast algorithm to find all points inside a rectangle
给定一组不同的二维点 space 和一个矩形(所有四个点的坐标,与 xy 轴平行的边)如何快速找到矩形内的点?
我对遍历所有点并查看哪个点在矩形内的基本解决方案不感兴趣。我正在寻找的是一种算法,它可以为我提供每个矩形的快速查询时间。
我不在乎在预处理步骤上花费了多少时间。我关心的是,在我处理完数据后,我获得了一个有用的结构,它为我提供了每个矩形的快速查询时间。
我知道例如我可以在 O(logN) 中计算出我在矩形内有多少个点。这是有效的,因为我在开始时做了很多繁重的处理,然后每次都用一个新的矩形查询处理过的数据,并在 logN 时间内获得一个新的计数。我正在寻找一种类似的算法来查找实际点数,而不仅仅是它们的计数。
我认为您应该将积分存储在 quadtree 中。我还没有弄清楚细节,但它应该允许基本上做一些类似于二进制搜索的事情,直接产生矩形内的点。
如果您的点是聚类的,即在一个小区域中存在包含许多点的聚类,而其他区域不包含或包含很少的点,R-tree 可能会更好。
我认为运行时复杂度应该是 O(logN)。
您可以在扇区中对点进行分组。如果一个扇区完全在给定的矩形之内或之外,那么它内的所有点也都在或之外。如果一个扇区部分在其中,那么您必须在 O(n) 中搜索该扇区中的点以检查它们是否在矩形中。寻找 k-d tree 搜索。
经典答案是 kD 树(在本例中为 2D 树)。
对于一个简单的替代方案,如果你的点分布足够均匀,你可以尝试网格化。
为正方形网格选择像元大小(如果问题是各向异性的,请使用矩形网格)。将每个点分配给包含它的网格单元,存储在链表中。当您执行查询时,找到所有被矩形重叠的单元格并扫描它们以遍历它们的列表。对于部分覆盖的单元格,您将需要执行矩形点测试。
大小的选择很重要:太大会导致无论如何都需要测试太多点;太小会导致空单元格过多。
您正在寻找 kd-tree range search 或范围查询。
- 当点集不断变化时,四叉树(或八叉树或 16 叉树...)会更好,但您希望分布是均匀的。不需要进一步的平衡步骤,因为树的结构是固定的。
- kd-trees 在一组固定的点上表现更好,即使分布不均匀。当点集发生变化时,很难(但并非不可能)进行自平衡步骤。
- AABB 树(或胖 AABB 树)提供了一种快速测试重叠形状的方法,而不仅仅是点。 AABB 树偶尔需要一些平衡。当包含不断移动的对象时,常用的方法是使用 "fat AABB-s",这样您就不必在每一帧都更新树。
- 仅按一个轴排序并使用二进制搜索(类似于 abelenky 的建议,但我认为进行第二次二进制搜索没有意义)是一种简单而优雅的解决方案,但是当您排序时会很慢通过 X 轴,并且所有点都在与 Y 平行的直线上。您必须对 X 的二进制搜索匹配的点进行线性过滤。时间复杂度是最坏的情况
O(n)
,但是这最坏的情况经常发生。
所有这些算法 运行 平均查询 O(log n + k)
其中 k 是匹配点的计数。
像 Yves 建议的那样,网格化可以在 O(k)
时间内执行范围搜索,但前提是查询矩形的大小是有界的。这是他们在particle simulations经常做的事情。即使输入集没有边界,也可以使用网格化——只需根据网格坐标的散列对桶进行固定计数。但是如果查询矩形可以是任意大小,那么网格化是行不通的。
连同其他答案,您还可以查看 Morton 代码(z-order 曲线排序)。
在你的情况下,这是静态数据,你甚至可以将整个点数据表示为一个数组。
https://en.wikipedia.org/wiki/Z-order_curve
这篇论文还有一个比较复杂的时间线,不同"multi-dimentional access methods" --http://www.cc.gatech.edu/computing/Database/readinggroup/articles/p170-gaede.pdf
给定一组不同的二维点 space 和一个矩形(所有四个点的坐标,与 xy 轴平行的边)如何快速找到矩形内的点?
我对遍历所有点并查看哪个点在矩形内的基本解决方案不感兴趣。我正在寻找的是一种算法,它可以为我提供每个矩形的快速查询时间。
我不在乎在预处理步骤上花费了多少时间。我关心的是,在我处理完数据后,我获得了一个有用的结构,它为我提供了每个矩形的快速查询时间。
我知道例如我可以在 O(logN) 中计算出我在矩形内有多少个点。这是有效的,因为我在开始时做了很多繁重的处理,然后每次都用一个新的矩形查询处理过的数据,并在 logN 时间内获得一个新的计数。我正在寻找一种类似的算法来查找实际点数,而不仅仅是它们的计数。
我认为您应该将积分存储在 quadtree 中。我还没有弄清楚细节,但它应该允许基本上做一些类似于二进制搜索的事情,直接产生矩形内的点。
如果您的点是聚类的,即在一个小区域中存在包含许多点的聚类,而其他区域不包含或包含很少的点,R-tree 可能会更好。
我认为运行时复杂度应该是 O(logN)。
您可以在扇区中对点进行分组。如果一个扇区完全在给定的矩形之内或之外,那么它内的所有点也都在或之外。如果一个扇区部分在其中,那么您必须在 O(n) 中搜索该扇区中的点以检查它们是否在矩形中。寻找 k-d tree 搜索。
经典答案是 kD 树(在本例中为 2D 树)。
对于一个简单的替代方案,如果你的点分布足够均匀,你可以尝试网格化。
为正方形网格选择像元大小(如果问题是各向异性的,请使用矩形网格)。将每个点分配给包含它的网格单元,存储在链表中。当您执行查询时,找到所有被矩形重叠的单元格并扫描它们以遍历它们的列表。对于部分覆盖的单元格,您将需要执行矩形点测试。
大小的选择很重要:太大会导致无论如何都需要测试太多点;太小会导致空单元格过多。
您正在寻找 kd-tree range search 或范围查询。
- 当点集不断变化时,四叉树(或八叉树或 16 叉树...)会更好,但您希望分布是均匀的。不需要进一步的平衡步骤,因为树的结构是固定的。
- kd-trees 在一组固定的点上表现更好,即使分布不均匀。当点集发生变化时,很难(但并非不可能)进行自平衡步骤。
- AABB 树(或胖 AABB 树)提供了一种快速测试重叠形状的方法,而不仅仅是点。 AABB 树偶尔需要一些平衡。当包含不断移动的对象时,常用的方法是使用 "fat AABB-s",这样您就不必在每一帧都更新树。
- 仅按一个轴排序并使用二进制搜索(类似于 abelenky 的建议,但我认为进行第二次二进制搜索没有意义)是一种简单而优雅的解决方案,但是当您排序时会很慢通过 X 轴,并且所有点都在与 Y 平行的直线上。您必须对 X 的二进制搜索匹配的点进行线性过滤。时间复杂度是最坏的情况
O(n)
,但是这最坏的情况经常发生。
所有这些算法 运行 平均查询 O(log n + k)
其中 k 是匹配点的计数。
像 Yves 建议的那样,网格化可以在 O(k)
时间内执行范围搜索,但前提是查询矩形的大小是有界的。这是他们在particle simulations经常做的事情。即使输入集没有边界,也可以使用网格化——只需根据网格坐标的散列对桶进行固定计数。但是如果查询矩形可以是任意大小,那么网格化是行不通的。
连同其他答案,您还可以查看 Morton 代码(z-order 曲线排序)。
在你的情况下,这是静态数据,你甚至可以将整个点数据表示为一个数组。
https://en.wikipedia.org/wiki/Z-order_curve
这篇论文还有一个比较复杂的时间线,不同"multi-dimentional access methods" --http://www.cc.gatech.edu/computing/Database/readinggroup/articles/p170-gaede.pdf