【问题标题】:Max square size for unknown number inside rectangle矩形内未知数的最大正方形大小
【发布时间】:2012-08-29 19:55:47
【问题描述】:

如果我有一组可以是任意数字的图块(正方形)并且它们要填充一个未知大小的容器(矩形),我如何计算出图块的最大尺寸而不使它们重叠。

因此,如果我有 2 个图块并且矩形为 100 * 100,则最大图块大小为 50 * 50。如果此大小的矩形有 3 或 4 个图块,这也将是图块的最大尺寸,即所以在这个例子中恰好是一个正方形。

如果矩形是 100 * 30 并且我有 2 个图块,则正方形的最大尺寸为 30 * 30,如果我有 4 个图块,则最大尺寸为 25 * 25。

我怎样才能以编程方式做到这一点,而无需通过所有可能的组合来占用处理器。


我试着总结得更好一点, 我有一个:

我需要在不重叠的情况下尽可能填充矩形/边界框。

我知道矩形的高度和宽度(但这可能会在运行时发生变化)。

我有 X 个图块(这可以在运行时更改),这些是正方形。

所有图块都不应重叠,每个图块的最大尺寸是多少。它们的大小都应相同。

【问题讨论】:

  • 所以您知道矩形大小和图块数量,并且您正在尝试计算最大图块大小?那是对的吗?您的函数签名看起来像: int GetTileSize(int width, int height, int tileCount)
  • 是的,没错。我知道矩形的高度和宽度,我希望找到一组瓷砖(正方形)的最大尺寸

标签: math tiles max-size


【解决方案1】:

概念上:

  • 从 1 个正方形开始
  • 对于每个额外的方格,如果您没有 到目前为止,您的网格框中的空间,缩小 现有的盒子足以制作 额外的行或列的空间。

伪代码:给定 M x N 矩形以填充 K 个正方形

// initial candidate grid within the rectangle
h=1
w=1
maxsquares=1
size=min(M,N) //size of the squares
while K > maxsquares
  if M/(h+1) >= N/(w+1)
    h=h+1
  else
    w=w+1
  endif
  maxsquares=h*w
  size=min(M/h,N/w)
done
print size

对于非常大的 K 来说,可能有更快的方法可以找到答案,但我想不出它们。如果你知道 M 和 N 是整数,可能还有更快的方法。

【讨论】:

  • 奇怪的是,我一直在思考这个问题,我总是从取一个小正方形并将其变大,但考虑取最大可能的正方形并将其变小帮助我解决了这个问题:)
【解决方案2】:

这是一个没有循环的 O(1) 解决方案。

使用矩形的纵横比(高度/宽度),您可以初步猜测 x 和 y 方向上的图块数量。这给出了瓦片总数的上限和下限:在 xy 和 (x+1)(y+1) 之间。

基于这些界限,有三种可能:

  1. 下限正确。计算将产生 xy 切片的最大 tileSize。
  2. 上限正确。计算将产生 (x+1)(y+1) 个图块的最大 tileSize
  3. 正确答案位于上限和下限之间。求解方程以确定 x 和 y 的正确值,然后计算最大的 tileSize 将导致正确数量的图块
int GetTileSize(int width, int height, int tileCount)
{
    // quick bailout for invalid input
    if (width*height < tileCount) { return 0; }

    // come up with an initial guess
    double aspect = (double)height/width;
    double xf = sqrtf(tileCount/aspect);
    double yf = xf*aspect;
    int x = max(1.0, floor(xf));
    int y = max(1.0, floor(yf));
    int x_size = floor((double)width/x);
    int y_size = floor((double)height/y);
    int tileSize = min(x_size, y_size);

    // test our guess:
    x = floor((double)width/tileSize);
    y = floor((double)height/tileSize);
    if (x*y < tileCount) // we guessed too high
    {
        if (((x+1)*y < tileCount) && (x*(y+1) < tileCount))
        {
            // case 2: the upper bound is correct
            //         compute the tileSize that will
            //         result in (x+1)*(y+1) tiles
            x_size = floor((double)width/(x+1));
            y_size = floor((double)height/(y+1));
            tileSize = min(x_size, y_size);
        }
        else
        {
            // case 3: solve an equation to determine
            //         the final x and y dimensions
            //         and then compute the tileSize
            //         that results in those dimensions
            int test_x = ceil((double)tileCount/y);
            int test_y = ceil((double)tileCount/x);
            x_size = min(floor((double)width/test_x), floor((double)height/y));
            y_size = min(floor((double)width/x), floor((double)height/test_y));
            tileSize = max(x_size, y_size);
        }
    }

    return tileSize;
}

我已经使用以下代码测试了此函数的所有整数宽度、高度和 tileCounts 介于​​ 1 和 1000 之间:

for (width = 1 to 1000)
{
    for (height = 1 to 1000)
    {
        for (tileCount = 1 to 1000)
        {
            tileSize = GetTileSize(width, height, tileCount);

            // verify that increasing the tileSize by one
            // will result in too few tiles
            x = floor((double)width/(tileSize+1));
            y = floor((double)height/(tileSize+1));
            assert(x*y < tileCount);

            // verify that the computed tileSize actually
            // results in the correct tileCount
            if (tileSize > 0)
            {
                x = floor((double)width/tileSize);
                y = floor((double)height/tileSize);
                assert(x*y >= tileCount);
            }
        }
    }
}

【讨论】:

    【解决方案3】:

    这是一个包装问题。很难找到最佳解决方案。参见例如Packing N squares in a square

    您可以通过将总面积除以平方数来计算(乐观的)上限:sqrt(width*height/n)

    【讨论】:

    • 啊,我没有注意到...缺少一条重要信息:瓷砖的对齐方式是否固定?下面给出的一些解决方案暗示了基于网格的解决方案,但您上面的示例不足以确定这是否是问题的一部分。我(也许愚蠢地)假设它是。
    • 好的链接,我的问题不需要任何方形瓷砖的旋转(从来没有想过尝试)
    • sqrt(width*height/n) 可以适用于所有矩形布局。当除以 n 时,尽可能多的 nos 会产生一些提醒。那将是未使用的空间。也从不会产生任何提醒的大量 nos 中,不会有一个完美的 sqrt,这也会给一些其他未使用的空间。甚至尝试使用 900 单位面积(100 宽度和 9 高度)的公式,并尝试将 9 个元素与您的公式相匹配。你不能创建 9 个 10*10 的正方形。我认为@ZacThompson 的回答非常有效。请参阅此帖子stackoverflow.com/questions/34154643/…
    【解决方案4】:

    我设法想出了一个“相对”的最佳解决方案。部分基于 Zac 的伪代码答案。

            //total number of tiles
            var tile_count : Number = numberOfSlides;
            //height of rectangle
            var b : Number = unscaledHeight;
            //width of rectanlge
            var a : Number = unscaledWidth;
    
            //divide the area but the number of tiles to get the max area a tile could cover
            //this optimal size for a tile will more often than not make the tiles overlap, but
            //a tile can never be bigger than this size
            var maxSize : Number = Math.sqrt((b * a) / tile_count);
            //find the number of whole tiles that can fit into the height
            var numberOfPossibleWholeTilesH : Number = Math.floor(b / maxSize);
            //find the number of whole tiles that can fit into the width
            var numberOfPossibleWholeTilesW : Number = Math.floor(a / maxSize);
            //works out how many whole tiles this configuration can hold
            var total : Number = numberOfPossibleWholeTilesH * numberOfPossibleWholeTilesW;
    
            //if the number of number of whole tiles that the max size tile ends up with is less than the require number of 
            //tiles, make the maxSize smaller and recaluate
            while(total < tile_count){
                maxSize--;
                numberOfPossibleWholeTilesH = Math.floor(b / maxSize);
                numberOfPossibleWholeTilesW = Math.floor(a / maxSize);
                total = numberOfPossibleWholeTilesH * numberOfPossibleWholeTilesW;
            }
    
            return maxSize;
    

    它的作用是计算矩形的总面积,然后将其除以所需的瓷砖数量。由于每个图块都是一个正方形,因此我可以对它进行 SQRT,以便获得最佳图块的最大尺寸。

    使用这个最佳尺寸,然后我会检查宽度和高度可以容纳多少个整体图块。将它们相乘,如果小于所需的瓷砖数量,则我会减小最佳尺寸并再次执行检查,直到所有瓷砖都适合矩形。

    我可以通过做一些事情来进一步优化这一点,例如每次将最佳尺寸减少 -2 或 -1,然后如果所有瓷砖都适合,则增加 1,以确保我没有错过有效尺寸。或者我可以跳回超过-2,比如-10,然后如果它们所有的瓷砖都适合增加5,那么如果不适合减少-2等等,直到我得到最佳配合。

    查看http://kennethsutherland.com/flex/stackover/SlideSorterOK.html 以获取我的示例。 感谢您提供各种信息。

    【讨论】:

    • 我发布了一个不使用任何循环的解决方案。我意识到这个问题是很久以前提出的,但这是一个有趣的问题要解决:) stackoverflow.com/questions/868997/…
    • @e.James 这个答案在某些情况下会产生错误的尺寸。
    【解决方案5】:

    以下函数计算给定信息的最大尺寸图块。

    如果它是用 Python 编写的,让您难以理解,请在评论中告诉我,我会尝试用其他语言编写。

    import math
    from __future__ import division
    
    def max_tile_size(tile_count, rect_size):
        """
        Determine the maximum sized tile possible.
    
        Keyword arguments:
        tile_count -- Number of tiles to fit
        rect_size -- 2-tuple of rectangle size as (width, height)
        """
    
        # If the rectangle is taller than it is wide, reverse its dimensions
        if rect_size[0] < rect_size[1]:
            rect_size = rect_size[1], rect_size[0]
    
        # Rectangle aspect ratio
        rect_ar = rect_size[0] / rect_size[1]
    
        # tiles_max_height is the square root of tile_count, rounded up
        tiles_max_height = math.ceil(math.sqrt(tile_count))
    
        best_tile_size = 0
    
        # i in the range [1, tile_max_height], inclusive
        for i in range(1, tiles_max_height + 1):
    
            # tiles_used is the arrangement of tiles (width, height)
            tiles_used = math.ceil(tile_count / i), i
    
            # tiles_ar is the aspect ratio of this arrangement
            tiles_ar = tiles_used[0] / tiles_used[1]
    
            # Calculate the size of each tile
            # Tile pattern is flatter than rectangle
            if tile_ar > rect_ar:
                tile_size = rect_size[0] / tiles_used[0]
            # Tile pattern is skinnier than rectangle
            else:
                tile_size = rect_size[1] / tiles_used[1]
    
            # Check if this is the best answer so far
            if tile_size > best_tile_size:
                best_tile_size = tile_size
    
        return best_tile_size
    
    print max_tile_size(4, (100, 100))
    

    算法可以大致描述如下

    • 如果矩形的高度大于宽度,则将其翻转,使其宽度大于高度。
    • 计算s,即图块数的平方根,四舍五入。 (在代码中命名为 tiles_max_height。)
    • i 从 1 到 s 的循环:
      • 构建一个由块数 / i 个正方形宽和i 个正方形高的正方形网格。 (将所有内容四舍五入。这会“填充”缺失的图块,例如,当您的图块总数为 3 时,使用 2 个图块乘 2 个图块。)
      • 使这个网格尽可能大。 (使用纵横比计算。)确定一个图块的大小。
      • 使用该尺寸,确定瓷砖覆盖的总面积。
      • 检查这是否是迄今为止最好的总面积;如果是,则存储正方形大小
    • 返回那个正方形大小

    这可能是此处列出的较快的算法之一,因为它可以为 n 个图块计算 O(sqrt(n)) 的最佳正方形大小。


    更新

    进一步考虑,这个问题有一个基于上述解决方案的更简单的解决方案。假设你有 30 个瓷砖。您可能的瓷砖排列很容易计算:

    • 30 x 1(纵横比 30.0000)
    • 15 x 2(纵横比 7.5000)
    • 10 x 3(纵横比 3.3333)
    • 8 x 4(纵横比 2.0000)
    • 6 x 5(纵横比 1.2000)
    • 6 x 6(纵横比 1.0000)

    假设您的矩形是 100 x 60。矩形的纵横比是 1.6667。这介于 1.2 和 2 之间。现在,您只需计算 8 x 4 和 6 x 5 排列的图块大小。

    虽然从技术上讲,第一步仍然需要 O(sqrt(n)),所以这个更新的方法并不比第一次尝试快。


    来自 cmets 线程的一些更新

    /*
    Changes made:
    
    tiles_used -> tiles_used_columns, tiles_used_rows
        (it was originally a 2-tuple in the form (colums, rows))
    */
    
    /* Determine the maximum sized tile possible. */
    private function wesleyGetTileSize() : Number {
        var tile_count : Number = slideCount.value;
        var b : Number = heightOfBox.value;
        var a : Number = widthOfBox.value;
        var ratio : Number;    
    
        // // If the rectangle is taller than it is wide, reverse its dimensions    
    
        if (a < b) {
            b = widthOfBox.value;
            a = heightOfBox.value;
        } 
    
        // Rectangle aspect ratio   
        ratio = a / b;    
    
        // tiles_max_height is the square root of tile_count, rounded up    
        var tiles_max_height : Number = Math.ceil(Math.sqrt(tile_count))    
        var tiles_used_columns : Number;
        var tiles_used_rows : Number;
        var tiles_ar : Number;
        var tile_size : Number;
    
        var best_tile_size : Number = 0;    
    
        // i in the range [1, tile_max_height], inclusive   
        for(var i: Number = 1; i <= tiles_max_height + 1; i++) {       
            // tiles_used is the arrangement of tiles (width, height)        
            tiles_used_columns = Math.ceil(tile_count / i);   
            tiles_used_rows = i;
    
            // tiles_ar is the aspect ratio of this arrangement        
            tiles_ar = tiles_used_columns / tiles_used_rows;        
    
            // Calculate the size of each tile        
            // Tile pattern is flatter than rectangle       
            if (tiles_ar > ratio){           
                tile_size = a / tiles_used[0]   ;
            }    
            // Tile pattern is skinnier than rectangle        
            else {            
                tile_size = b / tiles_used[1];
            }        
            // Check if this is the best answer so far        
            if (tile_size > best_tile_size){           
                best_tile_size = tile_size;
            }   
        }
    
        returnedSize.text = String(best_tile_size);
        return best_tile_size;
    }
    

    【讨论】:

    • 我认为 tile_dim 应该是用于 tiles_used。而且你真的不需要计算面积——检查你是否有迄今为止最大的瓷砖就足够了。这很好;比我的效率更高。但这让我更加确定有一条通往我们刚刚缺少的解决方案的直接途径:)
    • 感谢您的评论;这两个问题现在都已解决。这就是在深夜编码太晚的危险。为了效率,这个算法肯定可以改进。想到纵横比的查找表。 (实际上,它们会将复杂性提高到大约恒定的时间。)
    • 我已经试用了您的代码,这可能是我将其解释为 actionscript 的方式。查看kennethsutherland.com/flex/stackover/SlideSorterw.html 以获得视觉演示,并查看kennethsutherland.com/flex/stackover/wes/CalcForMaxSizeW.html 以获得函数和计算器(右键单击应用程序获取源代码)。所以它似乎有效,但只是一行(我只是误解了你的代码吗?)谢谢 - kenneth 21 秒前
    • 看看我粘贴的编辑过的 ActionScript。我认为两个最大的区别是您的 for 循环没有包含足够的代码,并且您正在使用 tiles_used 做一些更复杂的事情。在任何情况下,大括号现在都在正确的位置,并且变量已被分成两部分。试试看,让我知道它是否适合你!
    • 我找到了一种在 O(1) 中完成它的方法:stackoverflow.com/questions/868997/…
    【解决方案6】:

    您能详细说明一下您是如何定义填充的吗?如果我按照您的描述(大如果),您描述的许多情况似乎实际上并没有填充矩形。例如,您说 100*100 矩形中的 2 个正方形将是 50*50。如果我正确理解您的配置,它们将被放置在该矩形的“对角线”上。但是在那个矩形中也会有两个大小为 50*50 的“间隙”。这不是我认为的“填充”矩形。相反,我会将问题描述为边界框为 100*100 的 2 个(大小相等的正方形)的最大可能尺寸(假设每个正方形必须与至少一个其他正方形接触?)。

    这里的关键是你的矩形似乎是一个边界框并且没有被填充。

    另外,你能为这个计算写一个函数接口吗?给定边界框的尺寸,您是否需要对 n 个可能的正方形进行此操作?

    【讨论】:

    • 你不能用 2 个正方形填充一个 100 * 100 的矩形,你所能做的就是尽可能多地填充它,不要让任何正方形瓷砖重叠。我正在寻找未知数量的瓷砖的最大瓷砖尺寸来填充我知道它的宽度和高度的矩形(如果你喜欢,边界框)(但这些在运行时会发生变化,所以我不能硬编码它们)
    【解决方案7】:

    给定值:

    N - number of tiles
    a, b - sides of the rectangle
    

    可以使用此函数计算图块的侧面:

    def maxSize(n: Int, a: Int, b: Int) = {
      var l = 0
      for (i <- 1 until a.min(b)) { // 
        val newL = (a.min(b) / i).min( (a.max(b) * i)/n )
        if (l < newL && ((a.min(b)/newL) * (a.max(b)/newL) >= n )  )
          l = newL
      }
      return l
    }
    

    我假设你不会制作小于 1x1 的图块,无论 1 的度量是多少

    首先你从 0 号开始:

    l = 0
    

    然后你从 1 到 K 列的图块中迭代

    K = min(a, b)
    

    对于每次迭代,使用此公式计算图块的新最大边

    val newL = ( a.min(b) / i ).min( (a.max(b) * i)/n )
    

    这个公式取这两个值中较小的一个:

    1. min(a, b)/i -- maximum length of a tile if there are i columns in the smaller side of the rectangle
    2. (i * max(a, b))/n -- maximum length of a tile if there are i columns and n tiles in the bigger side of the rectangle
    

    如果候选newL大于初始值l,并且可以放在正方形中而不重叠的最大可能瓷砖数量大于或等于瓷砖数量n,那么

    l = newL
    

    最后返回 l

    【讨论】:

    • 看起来很有希望:) 作为一个动作脚本编码器,只是为了确定“for (i
    • for (var i = 0; i
    • 我看到你在我看到它和我发表评论之间编辑了这个。 'for (i
    • 哈哈,再次交叉评论。谢谢,我就是这么想的。
    • 你的函数有问题。对于大小为 650 * 1260 和 11 个图块的矩形,函数 maxSize(11, 650, 1260) 返回的图块的边是 216,结果是 229.09
    【解决方案8】:

    我假设正方形不能旋转。如果允许旋转它们,我很确定问题会非常困难。

    所以我们从左上角开始用正方形填充矩形。然后我们把正方形放在那个正方形的右边,直到我们到达矩形的右边,然后我们对下一行做同样的事情,直到我们到达底部。这就像在纸上写文字一样。

    请注意,永远不会出现右侧和底部留有空间的情况。如果两个方向都有空间,那么我们仍然可以增加正方形的大小。

    假设我们已经知道应该在第一行放置 10 个方格,并且这完全符合宽度。那么边长是width/10。所以我们可以在第一列放置m = height/sidelength 方块。这个公式可以说我们可以在第一列中放置 2.33 个正方形。不可能放置 0.33 个正方形,我们只能放置 2 个正方形。真正的公式是m = floor(height/sidelength)

    一种不是很快(但比尝试每种组合要快很多)的算法是先尝试在第一行/列上放置 1 个正方形,然后看看我们是否可以在矩形中放置足够多的正方形。如果它不起作用,我们会在第一行/列上尝试 2 个方格,依此类推,直到我们可以满足您想要的瓷砖数量。

    如果允许你在 O(1) 中进行算术,我认为存在一个 O(1) 算法,但我到目前为止还没有弄清楚。

    这是该算法的 Ruby 版本。如果矩形不是很薄,这个算法是 O(sqrt(# of tiles))。

    def squareside(height, width, tiles)
      n = 0
      while true
        n += 1
        # case 1: the squares fill the height of the rectangle perfectly with n squares
        side = height/n
        m = (width/side).floor # the number of squares that fill the width
        # we're done if we can place enough squares this way
        return side if n*m >= tiles
        # case 2: the squares fill the width of the rectangle perfectly with n squares
        side = width/n
        m = (height/side).floor
        return side if n*m >= tiles
      end
    end
    

    您也可以对这个算法使用二分搜索。在这种情况下,它是 O(log(# of tiles))。

    【讨论】:

      【解决方案9】:
      x = max(rectHeight/numberOfSquares, rectangleLength/numberOfSquares)
      
      if x <= retangleHeight && x <= rectangleLength then
        squareSideLength = x
      else
        squareSideLength = min(rectangleHeight, rectangleLength)
      

      【讨论】:

      • 我是这么想的,但它不适用于具有 4 个图块的 100x100 网格。如果有 4 个图块,x 可能是 50,而 x 将是 25。
      【解决方案10】:

      用长边除以瓷砖的数量。使用较短的一侧作为瓷砖尺寸。快! # 块瓷砖。

      Rectagle = 200 x 10
      Each tile is 10 x 10 (length of shorter side)
      200/10 = 20 (number of tiles needed)
      

      【讨论】:

      • 我认为在他的描述中,瓷砖的大小是未知的(并且给出了瓷砖数量)
      • 我知道瓷砖的数量,只是不知道尽可能多地填充它们所在的矩形的最佳尺寸。
      • 他还说容器的大小不知道。根据这些信息,将无法计算任何东西。我假设 2 个变量中的 1 个是已知的,并选择容器大小(这对我来说是合乎逻辑的)作为已知值。如果您不知道容器的大小或图块的大小,则无法根据它们的 # 来计算它们。
      • 你说得对,我最初的问题部分是模棱两可的。矩形大小是已知的,但它可以改变。对困惑感到抱歉。希望这些例子能让它更清楚一点。
      猜你喜欢
      • 2011-10-18
      • 1970-01-01
      • 2013-03-05
      • 1970-01-01
      • 1970-01-01
      • 2013-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多