找到重叠的矩形算法

时间:2011-10-11 14:35:05

标签: c++ algorithm computational-geometry

让我们说我有一大堆不重叠的矩形,带有整数坐标,一劳永逸地固定

我有另一个矩形A,其整数坐标的坐标正在移动(但你可以假设它的大小是常数)

查找哪些矩形交叉(或内部)A的最有效方法是什么? 我不能简单地遍历我的设置,因为它太大了。感谢

编辑:矩形都与轴平行

12 个答案:

答案 0 :(得分:9)

我敢打赌你可以使用quadtree的某种推导来做到这一点。看看this example.

答案 1 :(得分:4)

就个人而言,我会用KD-Tree或BIH-Tree解决这个问题。它们都是具有log(n)搜索时间的自适应空间数据结构。我为我的Ray Tracer实现了两个,并且他们尖叫。

- 更新 -

将所有固定矩形存储在KD树中。在测试交叉点时,按如下方式遍历KD树:

function FindRects(KDNode node, Rect searchRect, List<Rect> intersectionRects)

// searchRect is the rectangle you want to test intersections with
// node is the current node. This is a recursive function, so the first call
//    is the root node
// intersectionRects contains the list of rectangles intersected

int axis = node.Axis;

// Only child nodes actually have rects in them
if (node is child)
{
    // Test for intersections with each rectangle the node owns
    for each (Rect nRect in node.Rects)
    {
        if (nRect.Intersects(searchRect))
              intersectionRects.Add(nRect);
    }
}
else
{
    // If the searchRect's boundary extends into the left bi-section of the node
    // we need to search the left sub-tree for intersections
    if (searchRect[axis].Min  // Min would be the Rect.Left if axis == 0, 
                              // Rect.Top if axis == 1
                < node.Plane) // The absolute coordinate of the split plane
    {
        FindRects(node.LeftChild, searchRect, intersectionRects);
    }

    // If the searchRect's boundary extends into the right bi-section of the node
    // we need to search the right sub-tree for intersections
    if (searchRect[axis].Max  // Max would be the Rect.Right if axis == 0
                              // Rect.Bottom if axis == 1
                > node.Plane) // The absolute coordinate of the split plane
    {
        FindRects(node.RightChild, searchRect, intersectionRects);
    }
}

此函数在从伪代码转换后应该有效,但算法是正确的。这是一个log(n)搜索算法,可能是它的最慢实现(从递归到基于堆栈的转换)。

- 更新 - 添加了一个简单的KD树构建算法

包含面积/体积形状的最简单形式的KD树如下:

Rect bounds = ...; // Calculate the bounding area of all shapes you want to 
              // store in the tree
int plane = 0; // Start by splitting on the x axis

BuildTree(_root, plane, bounds, insertRects);

function BuildTree(KDNode node, int plane, Rect nodeBds, List<Rect> insertRects)

if (insertRects.size() < THRESHOLD /* Stop splitting when there are less than some
                                      number of rects. Experiment with this, but 3
                                      is usually a decent number */)
{
     AddRectsToNode(node, insertRects);
     node.IsLeaf = true;
     return;
}

float splitPos = nodeBds[plane].Min + (nodeBds[plane].Max - nodeBds[plane].Min) / 2;

// Once you have a split plane calculated, you want to split the insertRects list
// into a list of rectangles that have area left of the split plane, and a list of
// rects that have area to the right of the split plane.
// If a rect overlaps the split plane, add it to both lists
List<Rect> leftRects, rightRects;
FillLists(insertRects, splitPos, plane, leftRects, rightRects); 

Rect leftBds, rightBds; // Split the nodeBds rect into 2 rects along the split plane

KDNode leftChild, rightChild; // Initialize these
// Build out the left sub-tree
BuildTree(leftChild, (plane + 1) % NUM_DIMS, // 2 for a 2d tree
          leftBds, leftRects);
// Build out the right sub-tree
BuildTree(rightChild, (plane + 1) % NUM_DIMS,
          rightBds, rightRects);

node.LeftChild = leftChild;
node.RightChild = rightChild;

这里有一堆明显的优化,但构建时间通常并不像搜索时间那么重要。话虽这么说,一个良好的构建树是使搜索速度快的原因。如果你想学习如何建立一个快速的kd树,请查找SAH-KD-Tree。

答案 2 :(得分:2)

您可以创建两个矩形索引向量(因为两个对角点唯一地定义了您的矩形),并按其中一个坐标对它们进行排序。然后使用这两个索引数组搜索重叠,这将是对数而不是线性复杂度。

答案 3 :(得分:1)

你可以做一个随机的“行走”算法...基本上为你所有的固定位置矩形创建一个邻居列表。然后随机选择一个固定位置的矩形,并检查目标矩形与当前固定位置矩形的比较。如果它不在您随机选择的矩形内部作为起始点,那么它将位于与当前固定位置矩形的给定邻居相对应的八个方向中的一个方向上(即,对于任何给定的矩形,在该矩形中将存在矩形。 N,NE,E,SE,S,SW,W,NW方向)。选择与目标矩形最近的给定方向的相邻矩形,然后重新测试。这本质上是一个随机增量构造算法,它的性能往往非常适合几何问题(通常是单个迭代的对数,重复迭代的O(n log n))。

答案 4 :(得分:1)

创建一个包含“象限”元素的矩阵,其中每个象限代表系统中的N * M空间,N和M分别是最宽和最高矩形的宽度和高度。每个矩形将基于其左上角放置在象限元素中(因此,每个矩形将恰好在一个象限中)。给定一个矩形A,检查A自己的象限和8个相邻象限之间的矩形之间的碰撞。

这是一个我记得推荐的算法,建议将其作为游戏设计碰撞检测中暴力攻击测试的简单优化。当你主要处理小物体时它最有效,但是如果你有几个大物体,你可以通过分别对它们进行碰撞检测而不是将它们放在象限中来避免破坏它的效率,从而减少象限尺寸。

答案 5 :(得分:1)

由于它们没有重叠,我建议采用与Jason Moore(B)类似(但不相同)的方法。 按左上角的x排序数组。 然后按左上角的y排序。 (当然你只需要对它们进行排序以节省内存)。

现在你创建了两套Sliding_Window_X和Sliding_Window_Y。

使用二维搜索搜索x分类数组中的A窗口的x坐标(左上角)和y坐标。您将结果放入corrospondng Sliding_Window_Set。现在,您将在有序数组中添加以下矩形,这些矩形的x(y)(此时右下角)坐标低于A的右下角。

结果是你在Sliding_Window中设置了在一个坐标中与你的A重叠的窗口。 A的重叠是Sliding_Window_X和_Y的交集。

Sliding_Window集可以很容易地用2个数字表示(相应的有序数组的开始和结束索引)。

正如你所说移动 A,现在很容易重新计算重叠。根据方向,您现在可以向Sliding_Window集添加/删除元素。即你只从集合前面/末尾的排序数组中取出下一个元素,然后在结尾处删除。

答案 6 :(得分:1)

Topcoder提供了一种确定点是否位于矩形内的方法。它说我们有一个点x1,y1和一个矩形。我们应该选择一个随机点,它与矩形坐标系中的当前参考位置相距很远,例如x2,y2。

现在我们应该用点x1,y1和x2,y2创建一个线段。如果这个线段与给定矩形的奇数边相交(在我们的例子中它将是1,这个方法也可以扩展到一般多边形)然后点x1,y1位于矩形内部,如果它与偶数相交它位于矩形外面。

给定两个矩形,我们需要为1个三角形的每个顶点重复此过程,以便可能位于第二个三角形中。这样,即使它们没有与x轴或y轴对齐,我们也能够确定两个矩形是否重叠。

答案 7 :(得分:1)

间隔树:BST的设计是将“lo”值作为间隔中的关键。因此,例如,如果我们想在树中插入(23,46),我们将在BST中使用“23”插入它。

此外,对于每个节点的间隔树,我们保持以该节点为根的子树的最大端点(hi值)。

这种插​​入顺序允许我们在R(logN)时间内搜索所有“R”交叉点。 [我们在logN时间搜索第一个交叉点,在RlogN时间内搜索所有R]请参考间隔树文档,了解插入,搜索和复杂性的详细信息。

现在针对这个问题,我们使用一种称为扫描线算法的算法。想象一下,我们有一条垂直线(平行于y轴)扫描2D空间,并在此过程中与矩形相交。

1)通过优先级队列或通过排序,以x-cordinates(左边缘)的递增顺序排列矩形。复杂性NlogN,如果是N个矩形。

2)当这条线从左向右扫过时,以下是交叉情况:

  • 如果直线与未见过的矩形的左侧相交,则将矩形边的y坐标添加到间隔树中。 [say(x1,y1)和(x1,y2)是矩形添加间隔(y1,y2)到间隔树的左边坐标---&gt; (NlogN)

  • 在区间树上进行范围搜索。 [say(x1,y1)和(x1,y2)是矩形的左边坐标,取间隔(y1,y2)并在树上进行间隔交叉查询以找到所有交点] ---&gt; RlogN(在实践中)

  • 如果直线与矩形的右侧相交,则从间隔树中删除它的y坐标,因为矩形现在已完全处理。 ----&GT; NlogN

总复杂程度:NlogN + RlogN

答案 8 :(得分:0)

让你的矩形集为(Xi1,Yi1,Xi2,Yi2),其中i从0到N变化。

如果Ax1&gt;,矩形A和B不能相交。 Bx2 || Ay1&lt; By2 || Bx1> Ax2 || By1&lt; AY2。

创建针对范围/间隔优化的树(例如:分段树或区间树) 见http://w3.jouy.inra.fr/unites/miaj/public/vigneron/cs4235/l5cs4235.pdf

使用此树可在三角形更改坐标时查找三角形集。

答案 9 :(得分:0)

通过计算每个矩形的面积并检查长度L,高度H和矩形区域是否超过长度和高度以及矩形的面积A

答案 10 :(得分:0)

方法(A)

您可以使用interval treesegment tree。如果树被创建以便它们将被平衡,那么这将给你一个O(log n)的运行时间。我假设这种类型的预处理是实用的,因为它只会发生一次(一旦矩形开始移动,你似乎更关心运行时,而不是你第一次使用初始预处理量)。根据您的选择,空间量将为O(n)或O(n log n)。

方法(B)

鉴于你的大型矩形具有固定的大小并且从不改变它们的坐标并且它们是非重叠的,你可以尝试一种不同于其他人提出的算法/启发式风格(假设你可以使用一次性,前期预处理费)。

预处理算法[O(n log n)或O(n ^ 2)运行时{仅运行一次},O(n)空间]

  1. 使用您最喜欢的排序算法(我假设O(n log n)运行时间)将矩形按其水平坐标排序。
  2. 使用您最喜欢的排序算法(我假设O(n log n)运行时间)按照垂直坐标对矩形进行排序
  3. 计算水平坐标的probability distribution functioncumulative distribution function。 (O(1)到O(n ^ 2)的运行时间取决于使用的方法和数据的分布类型)

    a)如果你的矩形的水平坐标遵循一些自然发生的过程,那么你可以通过使用已知的分布来估计它们的分布函数(例如:normalexponentialuniform,等)。

    b)如果矩形的水平坐标不符合已知分布,则可以通过创建histogram来计算自定义/估算分布。

  4. 计算垂直坐标的probability distribution functioncumulative distribution function

    a)如果您的矩形的垂直坐标遵循一些自然发生的过程,那么您可以使用已知的分布来估计它们的分布函数(例如:normalexponentialuniform,等)。

    b)如果矩形的垂直坐标不遵循已知分布,则可以通过创建histogram来计算自定义/估计分布。

  5. 实时交点查找算法[从O(1)到O(log n)到O(n)的任何地方{注意:如果是O(n),那么n前面的常数将非常小}运行时间取决于分布函数与数据集的匹配程度]

    1. 获取移动矩形的水平坐标并将其插入到多个矩形的水平坐标的累积密度函数中。这将输出概率(0到1之间的值)。将此值乘以n(其中n是您拥有的许多矩形的数量)。此值将是要在已排序的矩形列表中检入的数组索引。如果此数组索引的矩形恰好相交,那么您就完成了,然后可以继续执行下一步。否则,您必须扫描周围的邻居以确定邻居是否与移动的矩形相交。您可以通过多种方式攻击此部分问题:

      a)进行线性扫描,直到找到交叉矩形或在移动矩形的另一侧找到一个矩形

      b)使用您计算的概率密度函数计算confidence interval,以便在潜在边界(即交叉点必须位于的区间)进行最佳猜测。然后在这个小间隔上进行二分查找。如果二进制搜索失败,则在部分(a)中恢复为线性搜索。

    2. 执行与步骤1相同的操作,但是对垂直部分而不是水平部分执行此操作。

    3. 如果步骤1产生了一个交点,并且步骤2产生了一个交点,并且步骤1中的交叉矩形与步骤2中的相同,则矩形必须与移动的矩形相交。否则没有交集。

答案 11 :(得分:0)

使用R +树,这很可能是您正在寻找的特定树结构。 R +树明确地不允许在内部(非叶)结构中重叠以换取速度。只要同时在多个叶子中不存在对象,就没有重叠。在您的实现中,不是支持重叠,只要需要将对象添加到多个叶子,只需返回true。

以下是数据结构的详细说明,包括如何管理矩形: The R+-tree: A dynamic index for multi-dimensional objects