快速计算圆圈内的点数

时间:2009-12-04 14:32:50

标签: algorithm math search geometry range

考虑到平面上有一组n个点,我想以某种方式比O(n ^ 2)(最好是O(nlog(n))更快地预处理这些点,然后能够回答以下类型的查询“在给定中心和半径的圆圈内有多少个n点?”比O(n)更快(O(最好是log(n))。

您能否提出一些我可以用于此问题的数据结构或算法?

我知道使用Voronoi图解决了这类问题,但我不知道如何在这里应用它。

6 个答案:

答案 0 :(得分:12)

构建空间细分结构,例如点的quadtreeKD-tree。在每个节点存储该节点所覆盖的点数。然后,当您需要计算查找圆覆盖的点时,遍历树,并在节点中的每个细分检查它是否完全在圆外,然后忽略它,如果它完全在圆内,则将其计数添加到如果它与圆相交,则递归,当你到达叶子时,检查叶子内的点以进行收容。

这仍然是O(n)最坏的情况(例如,如果所有点都位于圆周上),但是平均情况是O(log(n))。

答案 1 :(得分:8)

构建KD-tree点,这应该比O(n)更复杂,平均O(log(n))我认为。

您可以使用2D树,因为这些点被约束到一个平面。

假设我们已将问题转换为2D,我们将为这些点提供类似的内容:

 struct Node {
     Pos2 point;
     enum {
        X,
        Y
     } splitaxis;
     Node* greater;
     Node* less;
 };

greaterless包含沿splitaxis分别具有更大和更小坐标的点。

 void
 findPoints(Node* node, std::vector<Pos2>& result, const Pos2& origin, float radius) {
     if (squareDist(origin - node->point) < radius * radius) {
         result.push_back(node->point);
     }
     if (!node->greater) { //No children
          return;
     }
     if (node->splitaxis == X) {
         if (node->point.x - origin.x > radius) {
             findPoints(node->greater, result, origin radius);
             return;
         }
         if (node->point.x - origin.x < -radius) {
             findPoints(node->less, result, origin radius);
             return;
         }
         findPoints(node->greater, result, origin radius);
         findPoints(node->less, result, origin radius);
     } else {
         //Same for Y
     }
 }

然后用KD树的根

调用此函数

答案 2 :(得分:2)

如果我的目标是速度,并且点数不是很大(数百万),那么我将专注于内存占用和算法复杂性。

不平衡的k-d树在纸面上是最好的,但它需要指针,这可以将内存占用扩展3倍+,所以它已经用完了。

平衡的k-d树不需要存储,除了每个点有一个标量的数组。但它也有一个缺陷:标量不能量化 - 它们必须是与原始点相同的32位浮点数。如果它们被量化,则不再可能保证在阵列中较早出现的点要么在分裂平面上,要么保证在其左边,并且在阵列中稍后出现的点要么在分裂平面上,要么在右边。

我开发了一个解决这个问题的数据结构。 Synergetics人告诉我们音量在经验上是四向的。假设一架飞机在经验上同样是三向的。

我们习惯于通过四个方向-x,+ x,-y和+ y遍历一个平面,但是使用指向顶点的三个方向a,b和c更简单。等边三角形。

构建平衡k-d树时,将每个点投影到a,b和c轴上。通过增加a对点进行排序。对于中间点,舍入,量化和存储a。然后,对于中位数左侧和右侧的子阵列,通过增加b进行排序,对于中值点,进行舍入,量化和存储b。递归并重复,直到每个点都存储了一个值。

然后,在对结构测试圆(或其他)时,首先计算圆的最大 a,b和c坐标。这描述了一个三角形。在我们在最后一段中进行的数据结构中,将中间点的坐标与圆的最大坐标进行比较。如果点的a大于圆的a,我们可以取消中位数后的所有点的资格。然后,对于中位数的左右子阵(如果没有取消资格),将圆圈的b与中位点的b坐标进行比较。递归并重复,直到没有更多的点可以访问。

这在主题上类似于BIH data structure,但不需要-x和+ x以及-y和+ y的间隔,因为a,b和c在遍历平面方面同样出色,并且需要少一个方向去做。

答案 3 :(得分:1)

假设在坐标(x i ,y i )的笛卡尔平面上有一组点S,给定一个带中心的任意圆(x c ,y c )和半径r你想要找到那个圆圈中包含的所有点。

我还假设点和圆可能会移动,因此可以加快速度的某些静态结构不一定合适。

有三件事可以加速:

首先,您可以查看:

(xi-xc)^2 + (yi-yc)^2 <= r^2

而不是

sqrt((xi-xc)^2 + (yi-yc)^2) <= r

其次,你可以通过记住一个点只能在圆圈内来剔除点列表:

  • x i 在[x c -r,x c + r]的范围内;和
  • y i 在[y c -r,y c + r]的范围内;和

这称为边界框。您可以将其用作近似值或将点列表缩减为较小的子集,以便使用第一个等式进行精确检查。

最后,按x或y顺序对点进行排序,然后你可以进行二分搜索,找到可能位于边界框内的点集,进一步减少不必要的检查。

答案 4 :(得分:0)

取决于您预先计算的时间,您可以构建这样的树:

第一个节点分支是x值,下面是y值节点,下面是半径值节点。在每个叶子上都是一个点的哈希集。

当您想要计算x,y,r处的点时:遍历您的树并沿着与您的x,y值匹配的分支向下移动。当你到达根级别时,你需要做一点匹配(恒定时间的东西),但你可以找到一个半径,使得该圆圈中的所有点(由树中的路径定义)都在指定的圆圈内通过x,y,r和另一个圆(与之前的树中的x_tree,y_tree相同,但是不同的r_tree),使得原始圆中的所有点(由x,y,r指定)都在该圆中。 / p>

从那里开始,遍历两个树圈中较大的一个中的所有点:如果一个点在较小的圆圈中,则将其添加到结果中,如果不是,则对其进行距离检查。

唯一的问题是预先计算树需要很长时间。但是,您可以通过更改树中要包含的x,y和r分支数来指定要花费的时间。

答案 5 :(得分:0)

我使用了Andreas的代码,但它包含一个错误。例如,我在平面上有两个点[13,2],[13,-1],我的原点是[0,0],半径为100.它只找到1个点。这是我的修复:

void findPoints(Node * root, vector<Node*> & result, Node * origin, double radius, int currAxis = 0) {
if (root) {
    if (pow((root->coords[0] - origin->coords[0]), 2.0) + pow((root->coords[1] - origin->coords[1]), 2.0) < radius * radius) {
        result.push_back(root);
    }

    if (root->coords[currAxis] - origin->coords[currAxis] > radius) {
        findPoints(root->right, result, origin, radius, (currAxis + 1) % 2);
        return;
    }
    if (origin->coords[currAxis] - root->coords[currAxis] > radius) {
        findPoints(root->left, result, origin, radius, (currAxis + 1) % 2);
        return;
    }
    findPoints(root->right, result, origin, radius, (currAxis + 1) % 2);
    findPoints(root->left, result, origin, radius, (currAxis + 1) % 2);
}
}

不同之处是Andreas现在检查的孩子是否(!root-&gt; greater)不完整。另一方面,我不做那个检查,我只是检查根是否有效。 如果您发现了错误,请告诉我。