寻找重叠矩形算法

find overlapping rectangles algorithm

本文关键字:算法 重叠 寻找      更新时间:2023-10-16

假设我有一个巨大的不重叠矩形集合,它们的坐标是整型的,它们是固定的

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

找出哪些矩形与A相交(或在A内部)的最有效方法是什么?我不能简单地遍历我的集合,因为它太大了。由于

编辑:这些矩形都平行于轴

我敢打赌你可以使用某种四叉树的推导来做到这一点。看看这个例子。

我个人会用KD-Tree或BIH-Tree来解决这个问题。它们都是具有log(n)搜索时间的自适应空间数据结构。我有一个实现,我的光线追踪器,他们尖叫。

——

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

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)的搜索算法,可能是它最慢的实现(从递归转换为基于堆栈)。

—UPDATE—添加了一个简单的KD-Tree构建算法

包含面积/体积形状的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。

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

你可以做一个随机的"walking"算法…基本上为所有固定位置的矩形创建一个邻居列表。然后随机选择一个固定位置矩形,并检查目标矩形与当前固定位置矩形的位置。如果它不在你随机选择的矩形内,那么它将位于与当前固定位置矩形的给定邻居对应的八个方向之一(即,对于任何给定的矩形,都会有一个位于N, NE, E, SE, S, SW, W, NW方向的矩形)。在最接近目标矩形的给定方向上选择相邻矩形,然后重新测试。这本质上是一种随机增量构造算法,它的性能对于几何问题(对于单个迭代通常是对数的,对于重复迭代通常是O(n log n))来说非常好。

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

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

由于它们不重叠,我建议采用类似(但不等于)Jason Moore (B)的方法。按左上角的x对数组排序。然后对左上角的副本按y排序。(当然,为了节省内存,你只需要对指向它们的指针进行排序)。

现在创建两个集合Sliding_Window_X和Sliding_Window_Y。

在x排序数组中的A窗口的x坐标(左上角)和y坐标中使用二进制搜索进行搜索。将结果放入相应的Sliding_Window_Set中。现在,在有序数组中添加以下所有x(y)坐标(这次是右下)低于a的右下的矩形。

结果是在Sliding_Window-sets中有与A在一个坐标上重叠的窗口。A的重叠是Sliding_Window_X和_Y的交集。

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

当你说你移动 A时,现在很容易重新计算重叠。根据方向的不同,你现在可以添加/删除元素到Sliding_Window集合。也就是说,你只从集合的前端/末端的排序数组中取出下一个元素,并可能在最后删除。

Topcoder提供了一种方法来确定一个点是否位于矩形内。它说我们有一个点(x1,y1)和一个矩形。我们应该随机选择一个点,离当前参考点位置很远,在直角坐标系中,比如说x2,y2。

现在我们应该用点x1,y1和x2,y2做一条线段。如果这条线段与给定矩形的奇数条边相交(在我们的例子中是1,这种方法也可以推广到一般多边形),那么点x1,y1就在矩形内,如果它与偶数条边相交,它就在矩形外。

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

区间树:bst是否以'lo'值作为区间的键来设计?因此,例如,如果我们想在树中插入(23,46),我们将在BST中使用'23'插入它。

同样,对于每个节点上的区间树,我们保持在该节点上的子树的最大端点(hi值)。

这个插入顺序允许我们在R(logN)时间内搜索所有'R'个交叉点。[我们在logN时间内搜索第一个交集,在RlogN时间内搜索所有R]请参考间隔树文档,了解如何插入,搜索完成以及复杂性的细节。

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

1)通过优先队列或排序,以x坐标递增的顺序排列矩形(左沿)。复杂度NlogN (N个矩形)

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

  • 如果直线与从未见过的矩形的左侧相交,则将矩形一侧的y坐标添加到间隔树中。[假设(x1,y1)和(x1,y2)是矩形的左边缘坐标将区间(y1, y2)添加到区间树]--> (NlogN)

  • 对区间树进行范围搜索。[假设(x1,y1)和(x1,y2)是矩形的左边缘坐标,取区间(y1,y2)并对树进行区间相交查询以找到所有相交]--> RlogN(在实践中)

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

总复杂度:NlogN + RlogN

设矩形集合为(Xi1,Yi1,Xi2,Yi2),其中i从0变化到n

如果Ax1> Bx2 || Ay1

通过计算每个矩形的面积,并检查矩形的长度L、高度H和面积是否超过矩形a的长度、高度和面积

方法(A)

你可以使用区间树或段树。如果树是为了平衡而创建的,这将给你一个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. 计算横坐标的概率分布函数和累积分布函数。(运行时间为O(1)到O(n^2),具体取决于使用的方法和数据的分布类型)

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

    b)如果您的矩形的水平坐标不遵循已知的分布,那么您可以通过创建直方图来计算自定义/估计分布。

  4. 计算垂直坐标的概率分布函数和累积分布函数

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

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

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

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

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

    b)使用您计算的概率密度函数计算置信区间,以给出您对潜在边界的最佳猜测(即交集必须存在的区间)。然后在这个小区间内进行二叉搜索。如果二进制搜索失败,则返回到部分(a)中的线性搜索。

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

  3. 如果步骤1和步骤2产生相交,并且步骤1中相交的矩形与步骤2中的矩形相同,则该矩形必须与移动的矩形相交。

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

下面是数据结构的详细描述,包括如何管理矩形:R+-tree:多维对象的动态索引