在2d块网格中查找矩形

时间:2011-04-27 21:11:37

标签: algorithm grid

假设我有一个块网格,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 at(4,8)
  • 5x1 at(3,10)
  • 2x3 at(1,3)
  • 2x2 at(6,1)
  • 2x2 at(1,11)
  • 4x1 at(3,12)

请注意,矩形不能在每个其他空间中,它们不能重叠。例如,未提及(4,10)处的2x2矩形,因为它将与(3,10)处的5x1矩形重叠。

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

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

我考虑过强制执行,但我需要算法尽可能快地执行,因为它需要在有限(移动)设备上的非常小的时间范围内执行很多。

我在互联网上看到很多关于矩形的问题,但我很惊讶这个问题还没有被问过。我觉得太难了,或者没有人想做过这样的事情?

4 个答案:

答案 0 :(得分:9)

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

  1. 运行this clever O(WH) algorithm以确定最大的矩形,而不是仅跟踪单个最大的矩形,而不是跟踪W * H矩阵中的每个(x,y)位置记录的宽度和高度(一个或全部) )左上角为(x,y)的最大矩形,随时更新这些值。
  2. 循环遍历此矩阵,将每个足够大的矩形添加到按区域排列的max-heap(宽度*高度)。
  3. 读取此堆中的条目;它们将以递减的面积顺序生产。读取左上角为(x,y)并且宽度为w和高度为h的每个条目时,将矩形中包含的每个w h位置标记为W H中的“used”位数组。从堆中读取矩形时,我们必须丢弃任何包含“已使用”方块的矩形,以避免产生重叠的矩形。仅仅针对“used”数组检查每个候选矩形的四个边就足够了,因为候选矩形与另一个矩形重叠的唯一另一种方式是,如果后一个矩形完全包含它,这是不可能的,因为我们正在以递减的面积顺序读取矩形。
  4. 这种方法是“贪婪的”,因为如果有多种方法可以将纯色区域划分为最大矩形,则无法保证选择最大的矩形序列。 (例如,可能有几个矩形的左上角位于(10,10),其面积为16:16x1,8x2,4x4,2x8,1x16。在这种情况下,一个选项可能会产生更大的矩形“下游“但我的算法并不保证做出这样的选择。”如果有必要,你可以使用回溯找到这个整体最佳矩形系列,但我怀疑在最坏的情况下这可能会非常慢。

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

答案 1 :(得分:0)

注意:这是在你试图找到最大的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),这是一个很小的成本。这将允许您搜索大矩形的最可能区域。

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

答案 2 :(得分:0)

我必须为我的第一人称射手解决一个非常类似的问题。我在输入中使用它:
[] [] [] [] [] [] [] []
[] [] [] [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建议的那样。

答案 3 :(得分:0)

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

Link to C++ sources

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

我们可以用两种方式拆分剩余区域,算法将检查两者,并将使用覆盖大部分区域的选项,因此它将执行两次递归调用 - 第一次计算区域,第二次填充输出数组。

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

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

编辑:我刚刚意识到递归检查两种分裂变体会将算法提升到阶乘复杂度,就像O(min(M,N)!)。所以我禁用了第二个区域分割,这使算法的复杂度大约为O(M⋅N·log(M⋅N))。