如何有效地找到具有特定大小的开放矩形?

Posted

技术标签:

【中文标题】如何有效地找到具有特定大小的开放矩形?【英文标题】:How to find open rectangle with specific size efficiently? 【发布时间】:2014-09-01 05:16:10 【问题描述】:

背景

我有一个矩形区域被分成正方形(这是我正在制作的游戏)。我在我的代码中将其表示为一个简单的二维 boolean 数组:

  ┌──┬──┬──┬──┬──┐
  │  │  │  │  │  │ X = X-value, also increasing width
  ├──┼──┼──┼──┼──┤ Y = Y-value, also increasing length
  │  │  │  │  │  │
  ├──┼──┼──┼──┼──┤
  │  │  │  │  │  │
  ├──┼──┼──┼──┼──┤
  │  │  │  │  │  │
  ├──┼──┼──┼──┼──┤
^ │  │  │  │  │  │
Y └──┴──┴──┴──┴──┘
   X >

一些正方形可以被建筑物等占据矩形,就像这个输入一样(** = 被占用):

┌──┬──┬──┬──┬──┐
│**│**│  │  │  │ 3 taken rectangles
├──┼──┼──┼──┼──┤
│**│**│**│**│  │
├──┼──┼──┼──┼──┤
│  │  │**│**│  │
├──┼──┼──┼──┼──┤
│  │  │**│**│  │
├──┼──┼──┼──┼──┤
│**│  │**│**│  │
└──┴──┴──┴──┴──┘

在 2D boolean 数组中,“已采用”方格设置为 true,“开放、未采用”方格设置为 false

我的问题

我需要找到所有具有特定大小的“开放”矩形(未拍摄)。这是因为我需要找到所有可能的空间来放置下一个建筑物。例如,从上图中,如果我想获得所有“开放”的 1x2 矩形,我应该获得以下输出:

┌──┬──┬──┬──┬──┐
│**│**│1 │12│2 │ 3 taken rectangles (input)
├──┼──┼──┼──┼──┤ 4 open 1x2 rectangles (output)
│**│**│**│**│  │ Open rectangles are numbered
├──┼──┼──┼──┼──┤ 
│3 │3 │**│**│  │ Rectangles can overlap.
├──┼──┼──┼──┼──┤ The '12' square represents the overlapping
│4 │4 │**│**│  │ of rectangle 1 and 2.
├──┼──┼──┼──┼──┤ 
│**│  │**│**│  │ (Haha, my diagram kind of looks like old Minesweeper)
└──┴──┴──┴──┴──┘

我做了什么

这是我测试过的(暴力搜索,代码是 C# 和一些伪代码):

List<Rectangle> findOpenSpaces(boolean[][] area, int width, int length) 
    List<Rectangle> openSpaces = new List<Rectangle>();
    boolean open = true;
    // Loop through all rectangles with size "width" and "length"
    for x = 0 to (total area length) - length 
        for y = 0 to (total area width) - width 
            // Check this rectangle to see if any squares are taken
            Rectangle r = new Rectangle(x, y, width, length);
            if checkRectangle(area, r) returns true 
                // rectangle was fully open, add to the list
                openSpaces.Add(r);
            
        
    
    return openSpaces;


boolean checkRectangleIsOpen(boolean[][] area, Rectangle r) 
    for i = r.x to r.width 
        for j = r.y to r.length 
            if area[i][j] is true 
                // means that this square in the rectangle is taken,
                // thus the rectangle is not fully open
                // so return false (not open)
                return false;
            
        
    
    return true; // no square in the rectangle was taken


struct Rectangle  // just a struct to help store values
    int x, y, width, length;

问题

上面的(伪)代码有效,但如果你看一下,是时候到O(n^4) :((我认为,因为有四个嵌套的for 循环,但我不是专家)。另外,在游戏中矩形区域的总大小是50x50,而不是像我这里的例子那样是5x5。每当我做这个操作时,游戏就会明显滞后。

我在 Google 上搜索过这个问题,但我不确定如何称呼这个问题。如果有人能向我展示一个有效的算法来完成这项任务,我将不胜感激。谢谢:)

【问题讨论】:

也许值得考虑其他数据结构。例如,如果放置在地图上的对象不是太多,您可以将它们放在一个列表中,这样当您放置一个新对象时,您只需在新对象和每个对象之间进行某种碰撞检测。列表中的对象。或者您只需将所有占用的坐标放在一个列表中。 显然降低强度是最好的,但如果您的总大小受到限制,那么您可以通过一些并行性使事情变得足够快。例如,使用位掩码表示板将允许您通过单个操作在两个相邻行的所有列之间执行 AND、OR、XOR 之类的操作。 “Bitboards”通常用于国际象棋和黑白棋等游戏中,用于快速计算合法移动等。 代码审查可能是一个更好的地方:codereview.stackexchange.com :) @1337 哦,还没听说过 Code Review,我觉得这更像是一个算法问题,所以我把它贴在这里:) @dudeprgm 很有趣,我得到了这是一个审查审核但没有通过,因为我在 cmets 中提出了这个建议而不是投票;) 【参考方案1】:

这是一个使用动态规划和集合论来降低时间复杂度的解决方案:-

1. Evaluate Free[i][j] which counts all free tiles in rectangle (i,j) to (n,n).
2. Rect[i][j] is whether there is rectangle (h,w) starting at (i,j).
3. Count[i][j] = Free[i][j] - Free[i][j+w] - Free[i+h][j] + Free[i+h][j+w]
4. Rect[i][j] = (Count[i][j] == h*w)

时间复杂度:-

Free[i][j] 可以通过以下方式再次使用 DP 进行评估:-

Free[i][j] = Row[i][j]+Col[i][j]+Free[i+1][j+1] 其中Row[i][j] 是从j 开始的i 行中的空闲磁贴计数,Col[i][j] 是从i 开始的列j 中的计数。 ColRow 数组都可以在 O(NM) 中再次使用 DP 进行评估。因此,在预计算 Col &amp; Row 之后,您可以在 O(NM) 中评估 Free[i][j]

Rect[i][j] 可以在预计算 Free[i][j] 后在 O(NM) 中计算。

Total Complexity :O(NM)

【讨论】:

【参考方案2】:

我对数学知之甚少,但每当我看到另一个点内的多个数据点的挑战时……从编程的角度来看,我很想尝试一个小对象。

如果不只是一个数组或矩阵,您将一个盒子编程为一个引用相邻对象的类。 在你的游戏启动时,你有一个相当繁忙的过程来构建包含所有对相邻框的交叉引用的行。

但是在游戏过程中,您一次只更新一个框 + (neighbors * defined-box-size)。 如果 defined-box-size 大于 2 - 或者您关心多个尺寸 - 它可能仍然有很多重复,但明显少于所有框的嵌套循环。

当您想在整个网格中找到“空”时,您只需在每个盒子上调用一次函数,因为答案已提前作为盒子对象状态维护。

【讨论】:

这有利于维护每个网格位置的“已使用或未使用”状态,但不适用于查找可用位置的连续块。 目的是存储......关于自我和每个邻居的知识,直到定义一个块的深度。【参考方案3】:

一些快速的思考会表明,您根本无法比O(NM) 更快地做到这一点,因为至少有那么多可能的输出。您已经有了O(N^2M^2) 的解决方案,所以让我们看看是否能找到O(NM)

我要做的是,而不是找到 可以 形成大小为 a x b 的矩形的位置,而是找到 不能 的位置。

也就是说,是这样的(伪代码):

for each taken square:
    for each rectangle starting position:
        if rectangle at starting position includes this taken square,
            mark starting position as non-tenable

如果您的矩形很小(如上面的1x2 示例),这(有效实现)就足够了。但是,对于不依赖于渐近速度的矩形大小的方法。 . .

考虑(x,y) 被占用。那么与这个点重叠的矩形本身就形成了一个矩形,在那个范围内什么都不能用。所以我们想将一个(离散的)范围标记为不可用。这可以通过二维Fenwick tree 来完成,每次更新或查询的成本为O(log N log M)。要将范围 (x1, y1, x2, y2) 标记为“正在使用”,我们:

    (x1, y1) 加一 将(x1, y2) 减一 将(x2, y1) 减一 将(x2, y2) 加一

所以我们最终对每个使用的正方形进行 4 次 2D Fenwick 树操作,其中最多有 O(NM)。然后,当我们完成后,我们检查每个可能的起始位置的值:如果它为零,那么它是一个有效的起始位置。如果不为零,则不能使用。

总费用:O(NM log N log M)。虽然您可能可以更快地执行此操作,但此方法的实现速度/大小比非常好(2-D Fenwick 树大约需要 10 行代码来实现)。

【讨论】:

"不能比 O(N^2) 更快,因为至少有那么多输出。"你的意思是你不能比 O(mn) 更快,其中mn 分别是网格的高度和宽度? 他假设 m=n。提示:通过巧妙地选择示例(例如:4x5 板)​​,您可以传达有关任务的隐含知识,这可以破坏这些错误的假设。 我有点假设N 是两个维度中的最大值。我的坏习惯,我会改掉的!谢谢。 :)

以上是关于如何有效地找到具有特定大小的开放矩形?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用python有效地填充“缺失时间模式”和“填充它们”具有特定值?

如何有效地找到点集合的边界框?

Visual C++编程技巧之七

如何有效地复制列表中的特定值

如何在具有特定宽度和高度的矩形中获取 svg 文本元素?

如何有效地输出具有相同内容的文件列表?