找到N个矩形重叠的有效方法

时间:2016-11-16 00:16:40

标签: python algorithm computational-geometry

我正在尝试找到一种有效的解决方案,用于查找n个矩形的重叠,其中矩形存储在两个单独的列表中。我们正在寻找double arr[11];中与Sub Bounds() Dim foo As CStruct_t, i As Long For i = LBound(foo.arr) To UBound(foo.arr) Debug.Print i Next End Sub 中的矩形重叠的所有矩形(反之亦然)。将第一个列表中的一个元素与第二个列表进行比较可能需要花费大量时间。我正在寻找一种有效的解决方案。

我有两个矩形列表

listA

是从类创建的:

listB

我需要将rect = Rectangle(10, 12, 56, 15) rect2 = Rectangle(0, 0,1, 15) rect3 = Rectangle (10, 12, 56, 15) listA = [rect, rect2] listB = [rect3] 中的矩形与import numpy as np import itertools as it class Rectangle(object): def __init__(self, left, right, bottom, top): self.left = left self.bottom = right self.right = bottom self.top = top def overlap(r1, r2): hoverlaps = True voverlaps = True if (r1.left > r2.right) or (r1.right < r2.left): hoverlaps = False if (r1.top < r2.bottom) or (r1.bottom > r2.top): voverlaps = False return hoverlaps and voverlaps 进行比较,代码就像这样效率非常低 - 逐一比较

listA

任何更有效的方法来处理这个问题?

8 个答案:

答案 0 :(得分:10)

首先:与computational geometry中的许多问题一样,指定生长顺序分析的参数需要小心:调用列表的长度 m n ,最差情况只是那些参数Ω(m×n),因为所有区域都可能重叠(在这方面,问题的算法是asymptotically optimal)。通常包括输出的大小: t = f(m,n,o)Output-sensitive algorithm)。
平凡地,f∈Ω(m + n + o)表示所提出的问题。

Line Sweep是一种通过一维减少几何问题的范例 - 从原始形式,从2D到1D,从平面到线。

想象一下飞机上的所有矩形,列表的颜色不同 现在在这个平面上扫描一条线 - 从左到右,传统上,无穷小进一步向右“对于低y坐标”(处理坐标增加 x -order,增加 y - 订单等于 x ) 对于所有扫描(或扫描),每种颜色保留一组表示当前x坐标处所有矩形的“y间隔”,从空开始。 (在支持插入,删除和枚举与查询间隔重叠的所有间隔的数据结构中:见下文。)
遇到矩形的左侧,将该段添加到数据结构中以获取其颜色。报告任何其他颜色的重叠间隔/矩形 在右侧,删除该段 根据“重叠”的定义,在右侧之前处理左侧 - 或者反过来。

有许多数据结构支持插入和删除间隔,并查找与查询间隔重叠的所有间隔。目前,我认为Augmented Search-Trees可能最容易理解,实施,测试,分析...... 使用它,枚举所有 o 交叉的轴对齐矩形(a,b)listAlistB应该可以在 O((m + n)log(m + n)+ o)时间和 O(m + n)空间。对于相当大的问题实例,避免需要多于线性空间的数据结构((原始)Segment Trees,对于一个与区间重叠有关的示例)。

算法设计的另一个范例是Divide&Conquer:在计算几何问题的情况下,选择一个维度可以将问题划分为独立的部分,并选择一个坐标使得“下面的坐标”的子问题和“坐标高于”在预期的运行时间内接近。很可能,需要解决另一个(和不同的)子问题“包括坐标”。当a)解决子问题的运行时间是“超对数线性”时,这往往是有益的,并且b)有一种廉价(线性)方法从子问题的解决方案构建整体解决方案。
这有助于同时解决问题,并且可以与任何其他方法一起用于子问题,包括线扫描。

有很多方法可以调整每种方法,首先忽略不可能对输出做出贡献的输入项。为了“公平地”比较类似增长顺序的算法的实现,不要追求公平的“调整水平”:尝试投入大量时间进行调整。

答案 1 :(得分:5)

一些潜在的小的效率改进。首先,修复您的overlap()函数,它可能无需进行计算:

def overlap(r1, r2):

    if r1.left > r2.right or r1.right < r2.left:
        return False

    if r1.top < r2.bottom or r1.bottom > r2.top:
        return False

    return True

其次,计算其中一个列表的控制矩形并使用它来筛选另一个列表 - 任何不与容器重叠的矩形都不需要针对 all 进行测试有助于它的矩形:

def containing_rectangle(rectangles):
    return Rectangle(min(rectangles, key=lambda r: r.left).left,
        max(rectangles, key=lambda r: r.right).right,
        min(rectangles, key=lambda r: r.bottom).bottom,
        max(rectangles, key=lambda r: r.top).top
    )

c = containing_rectangle(listA)

for b in listB:
    if b.overlap(c):
        for a in listA:
            if b.overlap(a):

在我使用数百个随机矩形进行的测试中,这避免了对单个数字百分比(例如2%或3%)的比较,并且偶尔会增加比较次数。但是,大概你的数据不是随机的,而且这种筛选可能会更好。

根据数据的性质,您可以将其分解为容器矩形检查,检查50K中每批10K的矩形,或者切片可以为您提供最高效率。在将矩形分配给容器批次之前,可能会对矩形进行预分配(例如通过它们的中心)。

我们可以拆分并使用容器矩形批处理两个列表:

listAA = [listA[x:x + 10] for x in range(0, len(listA), 10)]

for i, arrays in enumerate(listAA):
    listAA[i] = [containing_rectangle(arrays)] + arrays

listBB = [listB[x:x + 10] for x in range(0, len(listB), 10)]

for i, arrays in enumerate(listBB):
    listBB[i] = [containing_rectangle(arrays)] + arrays

for bb in listBB:
    for aa in listAA:
        if bb[0].overlap(aa[0]):
            for b in bb[1:]:
                if b.overlap(aa[0]):
                    for a in aa[1:]:
                        if b.overlap(a):

使用我的随机数据,这减少了15%到20%的比较,甚至计算容器矩形比较。上面的矩形批处理是任意的,你可能会做得更好。

答案 2 :(得分:4)

您获得的异常来自您显示的代码的最后一行。表达式list[rect]无效,因为list是一个类,并且该上下文中的[]语法正在尝试对其进行索引。您可能只想要[rect](这会创建一个包含单个项目rect的新列表)。

您的代码还有其他一些基本问题。例如,您的Rect.__init__方法未设置left属性,这在您的碰撞测试方法中似乎是您所期望的。您还在r1方法的不同部分对r2overlap使用了不同的大小写(Python不认为r1R1相同})。

这些问题与测试两个以上的矩形没有任何关系,这是你的问题所要求的。最简单的方法(我强烈建议坚持使用简单的算法,如果您遇到上面提到的基本问题),只需使用现有的成对测试将每个矩形与其他矩形进行比较。您可以使用itertools.combinations轻松获取可迭代的所有项目对(如列表):

list_of_rects = [rect1, rect2, rect3, rect4] # assume these are defined elsewhere

for a, b in itertools.combinations(list_of_rects, 2):
    if a.overlap(b):
        # do whatever you want to do when two rectangles overlap here

答案 3 :(得分:3)

显然,如果您的列表(至少listB)按r2.xmin排序,您可以在listB中搜索r1.xmax并停止测试此listB中r1的重叠(其余部分将在右侧)。这将是O(n·log(n))。

排序向量的访问速度比排序列表快。

我假设矩形边缘的方向与轴相同。

同样修复你的overlap()函数,就像cdlane解释的那样。

答案 4 :(得分:2)

根据我的测试,使用numpy的这个实现大约快35到40倍。对于2个列表,每个列表有10000个随机矩形,这个方法需要2.5秒,问题中的方法需要大约90秒。就复杂性而言,它仍然是O(N ^ 2),就像问题中的方法一样。

import numpy as np

rects1=[
    [0,10,0,10],
    [0,100,0,100],
]

rects2=[
    [20,50,20,50],
    [200,500,200,500],
    [0,12,0,12]
]

data=np.asarray(rects2)


def find_overlaps(rect,data):
    data=data[data[::,0]<rect[1]]
    data=data[data[::,1]>rect[0]]
    data=data[data[::,2]<rect[3]]
    data=data[data[::,3]>rect[2]]
    return data


for rect in rects1:
    overlaps = find_overlaps(rect,data)
    for overlap in overlaps:
        pass#do something here

答案 5 :(得分:2)

如果您知道坐标的上限和下限,则可以通过将坐标空间划分为正方形来缩小搜索范围,例如: 100×100。

  • 制作一个&#34;设置&#34;每坐标平方。
  • 浏览所有方块,将它们放入&#34; set&#34;他们重叠的任何方格。

另请参阅Tiled Rendering,它使用分区来加速图形操作。

    // Stores rectangles which overlap (x, y)..(x+w-1, y+h-1)
    public class RectangleSet
    {
       private List<Rectangle> _overlaps;

       public RectangleSet(int x, int y, int w, int h);
    }

    // Partitions the coordinate space into squares
    public class CoordinateArea
    {
       private const int SquareSize = 100;

       public List<RectangleSet> Squares = new List<RectangleSet>();

       public CoordinateArea(int xmin, int ymin, int xmax, int ymax)
       {
          for (int x = xmin; x <= xmax; x += SquareSize)
          for (int y = ymin; y <= ymax; y += SquareSize)
          {
              Squares.Add(new RectangleSet(x, y, SquareSize, SquareSize);
          }
       }

       // Adds a list of rectangles to the coordinate space
       public void AddRectangles(IEnumerable<Rectangle> list)
       {
          foreach (Rectangle r in list)
          {
              foreach (RectangleSet set in Squares)
              {
                  if (r.Overlaps(set))
                      set.Add(r);
              }
          }
       }
    }

现在你有一组更小的矩形用于比较,这可以很好地加快速度。

CoordinateArea A = new CoordinateArea(-500, -500, +1000, +1000);
CoordinateArea B = new CoordinateArea(-500, -500, +1000, +1000);  // same limits for A, B

A.AddRectangles(listA);
B.AddRectangles(listB);

for (int i = 0; i < listA.Squares.Count; i++)
{
    RectangleSet setA = A[i];
    RectangleSet setB = B[i];

    // *** small number of rectangles, which you can now check thoroghly for overlaps ***

}

答案 6 :(得分:1)

我认为你必须设置一个额外的数据结构(空间索引),以便快速访问可能重叠的附近矩形,以便将时间复杂度从二次变为线性。

另见:

答案 7 :(得分:0)

这里是我用来计算许多候选矩形(带有候选人对象坐标[[l,t,r,b],...])与目标对象一个对象(target_coords [l,t,r,b]的重叠区域的方法) ):

comb_tensor = np.zeros((2, candidate_coords.shape[0], 4))

comb_tensor[0, :] = target_coords
comb_tensor[1] = candidate_coords

dx = np.amin(comb_tensor[:, :, 2].T, axis=1) - np.amax(comb_tensor[:, :, 0].T, axis=1)
dy = np.amin(comb_tensor[:, :, 3].T, axis=1) - np.amax(comb_tensor[:, :, 1].T, axis=1)

dx[dx < 0] = 0
dy[dy < 0] = 0 

overlap_areas = dx * dy

这应该是相当有效的,尤其是在有很多候选矩形的情况下,因为所有操作都是使用对ndarrays操作的numpy函数完成的。您可以循环计算重叠区域,也可以向comb_tensor添加一个尺寸。