【问题标题】:Finding rectangles in a 2d block grid在二维块网格中查找矩形
【发布时间】:2011-08-14 05:11:58
【问题描述】:

假设我有一个 7x12 的方块网格。我们使用颜色 '*'、'%'、'@' 和空单元格 '-'。

1 2 3 4 5 6 7
- - - - - - -  1
- - - - - - -  2
% % - - - - -  3
% % - - - - *  4 
% % - - - @ %  5
@ @ @ - - @ %  6
@ @ * * * - *  7
* * * % % % %  8 
% @ @ % * * %  9
% @ % % % % %  10
* * * % % @ @  11
* * @ @ @ @ *  12

我想在这个网格中找到某个最小尺寸的矩形,我能找到最大的,然后再变小,直到找不到大于或等于最小尺寸的矩形。

在此示例中,考虑最小尺寸 1x4、4x1、2x2,因此 1x3 无效,但 2x3 有效。如果我们想要最大的矩形,我们会找到以下内容:

  • 4x1 在 (4,8)
  • 5x1 在 (3,10)
  • 2x3 在 (1,3)
  • 2x2 在 (6,1)
  • 2x2 在 (1,11)
  • 4x1 在 (3,12)

请注意,矩形不能在彼此的空间中,它们不能重叠。例如,没有提到 (4,10) 处的 2x2 矩形,因为它会与 (3,10) 处的 5x1 矩形重叠。

所有都是完全有效的矩形:它们等于或大于最小尺寸,并且每个矩形的所有块都具有相同的颜色。

我想要以编程方式执行此操作。当您告诉某人在网格中查找矩形时,他会立即找到它们,而无需考虑。问题是,我怎样才能写出同样的算法?

我考虑过暴力破解,但我需要算法尽可能快地执行,因为它需要在有限的(移动)设备上在非常短的时间内执行很多次。

我在互联网上看到很多关于矩形的问题,但我很惊讶这个问题还没有被问到任何地方。是我想得太难了,还是从来没有人想做这样的事情?

【问题讨论】:

  • 矩形可以交叉吗?我看到你没有在 (4,10) 处提到 2x2,是因为它在 (3,10) 处与更大的 5x1 有一个共同的部分吗?
  • 不,矩形不能相互交叉。每个矩形不能在其他矩形的空间中。
  • @Sebeazz 不会根据矩形的顺序来取消资格吗?
  • @GlowCoder:不,因为我想先找到最大的。

标签: algorithm grid


【解决方案1】:

分别调用输入数组W和H的宽高。

  1. 运行this clever O(WH) algorithm 来确定最大矩形,但不是只跟踪单个最大矩形,而是为 W*H 矩阵中的每个 (x, y) 位置记录计算(一个或全部)矩形的宽度和高度左上角为 (x, y) 的最大矩形,随时更新这些值。
  2. 遍历此矩阵,将其中的每个足够大的矩形添加到按面积(宽度 * 高度)排序的 max-heap
  3. 从此堆中读取条目;它们将按面积递减顺序生产。读取左上角为 (x, y) 且宽度为 w 和高度为 h 的每个条目,将矩形中包含的每个 wh 位置标记为 WH 中的“已使用”位数组。从堆中读取矩形时,我们必须丢弃任何包含“已使用”正方形的矩形,以避免产生重叠的矩形。仅检查每个候选矩形的 四个边 与“已使用”数组就足够了,因为候选矩形可以与另一个矩形重叠的唯一另一种方式是,如果后一个矩形完全包含在它里面,这是不可能的,因为我们正在按面积递减顺序读取矩形。

这种方法是“贪婪的”,因为如果有多种方法可以将纯色区域雕刻成最大的矩形,则它不能保证选择整体上最大的矩形序列。 (例如,可能有几个矩形的左上角位于 (10, 10) 并且面积为 16:16x1、8x2、4x4、2x8、1x16。在这种情况下,一种选择可能会产生更大的矩形“下游”,但我的算法不能保证做出这样的选择。)如有必要,您可以使用回溯找到这个整体最优的矩形系列,但我怀疑在最坏的情况下这可能会非常慢。

我提到的最大矩形算法是为单色矩形设计的,但如果你不能让它适应你的多色问题,你可以在开始第 2 步之前对每种颜色运行一次。

【讨论】:

  • 您介意更新链接吗?它似乎不再起作用了。
  • @JanBerktold:起初我也这么认为——它超时了。但后来我为同一篇文章找到了不同的 URL:drdobbs.com/database/the-maximal-rectangle-problem/…。这在大约 30 秒后提出了这篇文章......并且对原始 URL 的第二次尝试也奏效了!所以我将链接保持原样。
  • 您能否进一步解释@j_random_hacker 如何执行您在第一步中描述的操作?我不确定 A)如何为算法提供 x/y 位置,B)从算法中获取每个矩形的数据的位置 - 我只看到最大/最大区域。
  • @j_random_hacker 两个链接对我来说仍然断开
  • 通常情况下,Archive.org 有一个副本 -- web.archive.org/web/20150921112543/http://www.drdobbs.com/…
【解决方案2】:

我自己的解决方案是找到最大的矩形,使用与@j_random_hacker 答案相同的algorithm,然后将剩余区域分成4个区域,并在每个区域中再次递归搜索最大的矩形。

Link to C++ sources

它会找到比接受的答案更少的矩形,因为我发现在搜索最大的矩形时很难采用该算法来保存每个中间矩形。 该算法会跳过所有较小的矩形,因此我们必须遍历网格中的每个点,以保存每个可能的矩形,然后丢弃较小的矩形,这会使算法回到 O(M³ ⋅ N³) 复杂度。

我们可以用两种方式分割剩余的区域,算法会检查两者,并且会使用覆盖最多区域的选项,所以它会执行两次递归调用——第一次计算面积,第二次填充输出数组。

    ****|***|***                ************
    ****|***|***                ************
    ****#####***                ----#####---
    ****#####***        vs      ****#####***
    ****#####***                ----#####---
    ****|***|***                ************

我们可以只选择一种区域分割来使算法运行得更快,因为这种区域比较并没有对检测到的矩形数量提供太大的改进,说实话。

编辑:我刚刚意识到递归检查两个分裂变体会将算法提高到阶乘复杂度,类似于 O(min(M,N)!)。所以我禁用了第二个区域分割,这使得算法的复杂度在 O(M⋅N⋅log(M⋅N)) 左右。

【讨论】:

    【解决方案3】:

    注意:这是在您试图找到最大的k 矩形的假设下运行的。

    我们知道,在最坏的情况下,我们必须至少查看网格中的每个节点一次。这意味着我们最好的最坏情况是O(len*wid)

    你的蛮力将是O(len*len*wid*wid),用“在一个点检查矩形是O(len*wid),你这样做O(len*wid)次。

    您可能发现情况并非如此,因为每次找到一个矩形时,您都有可能缩小问题空间。我觉得“检查每个矩形”的蛮力方法将是最好的方法。不过,您可以采取一些措施来加快速度。

    基本算法:

    for(x = 1 .. wid) {
        for(y = 1 .. len) {
            Rectangle rect = biggestRectOriginatingAt(x,y);
            // process this rectangle for being added
        }
    }
    
    • 跟踪最大的k 矩形。随着您的进行,您可以搜索符合条件的矩形可能所在的周长。

      Rectangle biggestRectOriginatingAt(x,y) {
          area = areaOf(smallestEligibleRectangle); // if we want the biggest k rect's, this
                                                    // returns the area of the kth biggest
                                                    // known rectangle thus far
      
          for(i = 1 .. area) {
              tempwid = i
              templen = area / i
      
              tempx = x + tempwid
              tempy = y + templen
      
              checkForRectangle(x,y,tempx,tempy); // does x,y --> tempx,tempy form a rectangle?
          }
      
      }
      

    这使您可以在大型搜索结束时获得巨大的性能提升(如果是小型搜索,您不会获得太多收益,但您不在乎,因为它是小型搜索!)

    这也不适用于更多随机分布。

    • 另一种优化是使用油漆填充算法来查找最大的连续区域。这是O(len*wid),这是一笔不小的费用。这将允许您在最有可能的区域中搜索大矩形。

    请注意,这些方法都不能减少最坏的情况。但是,它们确实减少了现实世界的预期运行时间。

    【讨论】:

      【解决方案4】:

      我必须为我的第一人称射击游戏解决一个非常相似的问题。我在输入中使用它:
      [  ][  ][  ][  ][  ][  ][  ][  ]
      [  ][  ][  ][X][  ][  ][  ][  ]
      [  ][X][X][X][X][X][X][X]
      [  ][  ][X][X][X][X][  ][  ]
      [  ][X][X][X][X][  ][  ][  ]
      [  ][X][X][X][X][  ][  ][  ]
      [  ][  ][X][  ][  ][  ][  ][  ]
      [  ][  ][  ][  ][  ][  ][  ][  ]

      我在输出中得到:
      [  ][  ][  ][  ][  ][  ][  ][  ]
      [  ][  ][  ][A][  ][  ][  ][  ]
      [  ][B][G][G][G][F][E][E]
      [  ][  ][G][G][G][F][  ][  ]
      [  ][D][G][G][G][  ][  ][  ]
      [  ][D][G][G][G][  ][  ][  ]
      [  ][  ][C][  ][  ][  ][  ][  ]
      [  ][  ][  ][  ][  ][  ][  ][  ]

      This schema 更好。源代码(在 GNU 通用公共许可证版本 2 下)是 here,它被大量注释。您可能需要像 j_random_hacker 建议的那样根据您的需要对其进行一些调整。

      【讨论】:

      • 相关代码具体在哪里? sourceforge 文件夹很大。
      • Jan,我刚刚修复了URL,包含相关代码的代码在ArrayHelper.computeFullArraysFromNonFullArray()中。
      • @JanBerktold 我已经评论了这个类中的所有内容,并且我创建了一个在输入中使用方形和矩形数组的单元测试。它不是很快,但它可以工作:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多