在2D数组中查找最大的矩形

时间:2011-05-09 01:56:12

标签: c++ arrays parsing

我需要一种能够解析2D数组并返回最大连续矩形的算法。作为参考,请看我演示我的问题的图像。

enter image description here

4 个答案:

答案 0 :(得分:10)

通常,您使用所谓的扫描线算法来解决这些问题。他们在您的案例候选矩形中一次检查一行(或扫描线)的数据,从而构建您正在寻找的答案。

这里概述了它如何运作。

从0..6对图像中的所有行进行编号,我将自下而上进行操作。

检查第0行你有两个矩形的开头(我假设你只对黑色方块感兴趣)。我将使用(x,y,width,height)来指代矩形。两个活动矩形是(1,0,2,1)和(4,0,6,1)。您将这些添加到活动矩形列表中。此列表按增加x坐标排序。

现在您已完成扫描线0,因此您可以增加扫描线。

检查第1行,您可以查看是否有以下任何行:

  • 新的活动矩形
  • 现有矩形生长的空间
  • 分裂现有矩形的障碍
  • 要求您从活动列表中删除矩形的障碍

当您沿着该行工作时,您将看到您有一个新的活动矩形(0,1,8,1),我们可以将现有的一个活动矩阵增长到(1,0,2,2)并且我们需要删除活动(4,0,6,1)用两个较窄的替换它。我们需要记住这个。这是我们迄今为止看到的最大规模。它被两个新的活动替换:(4,0,4,2)和(9,0,1,2)

因此,在发送扫描线1时,我们有:

  • 活动清单:(0,1,8,1),(1,0,2,2),(4,0,4,2),(9,0,1,2)
  • 目前为止最大的:(4,0,6,1)

以这种方式继续,直到扫描线用完为止。

棘手的部分是编写沿扫描线运行的例程,更新活动列表。如果你做得正确,你只会考虑每个像素一次。

希望这会有所帮助。描述它有点棘手。

答案 1 :(得分:5)

我喜欢这种区域增长方法。

  • 对于ARRAY中的每个空缺点
  • 尽可能远东地生长
  • 尽可能地向WEST发展
  • 通过添加行
  • 尽可能地增长NORTH
  • 通过添加行
  • 尽可能地扩展SOUTH
  • 保存所用种子像素的结果区域
  • 循环遍历ARRAY中的每个点后,选择面积最大结果的种子像素

......将是一个彻底的,但也许不是最有效的方式。

我想你需要回答一个哲学问题“点线是一个瘦的矩形吗?”如果一行==一个细长矩形,您可以进一步优化:

  • 创建第二个名为LINES的整数数组,其大小与ARRAY
  • 相同
  • 循环遍历ARRAY中的每个点
  • 确定从每个点开始的EAST的最长有效行,并将其长度保存在LINES的相应单元格中。
  • 对ARRAY中的每个点执行此操作后,循环执行LINES
  • 对于LINES中的每个点,确定有多少邻居SOUTH具有相同的长度值或更少。
  • 接受长度较小的南方邻居,如果这样做会增加矩形的面积。
  • 使用该种子点的最大矩形是(Number_of_acceptable_southern_neighbors * the_length_of_longest_accepted_line)
  • 计算每个种子的最大矩形区域时,检查是否有新的最大值并保存结果。
  • 并且......你可以在不分配数组LINES的情况下做到这一点,但我认为在我的解释中使用它会使描述更简单。
  • 而且......我觉得你需要用VERTICAL_LINES和EASTERN_NEIGHBORS来做同样的事情,或者有些情况下可能会错过那些又高又瘦的大矩形。所以也许第二种算法毕竟不是那么优化的。

使用第一种方法检查您的工作。我认为Knuth说“......过早的优化是万恶之源。”

HTH,

佩里


ADDENDUM:稍后进行了几次编辑,我认为这个答案值得小组投票。

答案 2 :(得分:2)

一个直接的方法是循环遍历网格中的所有潜在矩形,找出它们的区域,如果它大于当前最高区域,则选择它作为最高区域:

var biggestFound
for each potential rectangle:
    if area(this potential rectangle) > area(biggestFound)
        biggestFound = this potential rectangle

然后你只需要找到潜在的矩形。

for each square in grid:
    recursive loop 1:
        if not occupied:
            grow right until occupied, and return a rectangle
            grow down one and recurse (call loop 1)

这将复制很多工作(例如,你将重新评估很多子矩形),但它应该给你一个答案。

修改

另一种方法可能是从网格大小的单个正方形开始,然后“减去”占用的正方形,最后得到一组最终的潜在矩形。这里可能有使用quadtrees的优化机会,并确保您按顺序“按顺序”保持拆分矩形,从上到下,从左到右,以防您需要在算法中进一步重新组合矩形。 / p>

如果您实际上是从矩形数据(对于“填充网格”集)开始,而不是松散的像素网格,那么您可以轻松地从矩形/区域减法算法中获得更好的效果。

我不打算为此发布伪代码,因为这个想法完全是实验性的,我不知道对于松散的像素网格,perf是否会更好;)

Windows系统“区域”和“脏矩形”以及一般的“时间缓存”可能是更好的灵感来提高效率。如果这是一个图形算法,还有很多z缓冲技巧......

答案 3 :(得分:1)

使用动态编程方法。考虑函数S(x,y)使得S(x,y)保持最大矩形的区域,其中(x,y)是矩形的最右下角单元; x是行坐标,y是矩形的列坐标。

例如,在你的图中,S(1,1)= 1,S(1,2)= 2,S(2,1)= 2,S(2,2)= 4.但是,S (3,1)= 0,因为此单元格已填满。 S(8,5)= 40,表示最右边单元格为(8,5)的最大矩形区域为40,这恰好是本例中的最优解。

您可以从S(x-1,y),S(x,y-1)和S(x-1,y-1)的值轻松编写S(x,y)的动态编程方程。使用它可以在O(mn)时间内获得所有S(x,y)的值,其中m和n是给定表的行和列维度。一旦知道所有1< = x< = m的S(x,y),并且对于所有1< = y< = n,我们只需找到x和y,其中S(x, y)是最大的;此步骤也需要O(mn)时间。通过保留添加数据,您还可以找到最大矩形的边长。

整体复杂度为O(mn)。要了解更多相关信息,请阅读第15章或Cormen的算法手册,特别是第15.4节。