Максимальный квадратный размер для неизвестного номера внутри прямоугольника

Если у меня есть набор плиток (квадратов), которые могут быть любым числом, и они должны заполнить контейнер (прямоугольник) неизвестного размера, как мне определить максимальный размер плиток, не накладывая на них какого-либо из них.

Итак, если у меня есть 2 плитки, а прямоугольник равен 100 * 100, максимальный размер плитки составляет 50 * 50. Это также будет максимальный размер плитки, если было 3 или 4 плитки для этого размера rectanlgle, что просто поэтому в этом примере бывает квадрат.

Если rectanlge был 100 * 30, и у меня было 2 плитки, максимальный размер квадрата был бы 30 * 30, если у меня есть 4 плитки, максимальный размер будет 25 * 25.

Как я могу сделать это программно, не забивая процессор, пройдя все возможные комбинации.


Я пытаюсь подытожить немного лучше, У меня есть:

прямоугольник/ограничивающий прямоугольник, который мне нужно заполнить как можно больше без перекрытия плиток.

Я знаю высоту и ширину прямоугольника (но это может измениться во время выполнения).

У меня есть X количество фрагментов (это может измениться во время выполнения), это квадраты.

Ни одна из плиток не должна перекрываться, каков максимальный размер, который может быть у каждой плитки. Все они должны быть одного размера.

Ответы

Ответ 1

Мне удалось найти "относительно" оптимальное решение. Частично основано на ответе псевдокода 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 insted из -1 каждый раз, а затем, если все плитки увеличиваются на 1, чтобы убедиться, что я не пропустил допустимый размер. или я мог бы отскакивать больше, чем -2, скажем -10, тогда, если все плитки будут увеличиваться на 5, то если они не подходят, уменьшите на -2 и т.д., пока я не получу оптимальную подгонку.

Обратите внимание на http://kennethsutherland.com/flex/stackover/SlideSorterOK.html для моего примера. Спасибо за всю информацию.

Ответ 2

Это проблема упаковки. Оптимальных решений трудно найти. См. Например Упаковка N квадратов в квадрате.

Вы можете вычислить (оптимистичную) верхнюю границу, разделив общую поверхность на число квадратов: sqrt(width*height/n).

Ответ 3

Концептуально:

  • начать с 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 являются целыми числами, могут быть даже более быстрые методы.

Ответ 4

Вот решение O (1) без петель.

Используя соотношение сторон (высота/ширина) прямоугольника, вы можете придумать начальное предположение о количестве плиток в направлениях x и y. Это дает верхнюю и нижнюю границу для общего количества плиток: между xy и (x + 1) (y + 1).

Основываясь на этих ограничениях, существует три возможности:

  • Нижняя граница верна. Вычислите самый большой размер tileSize, который приведет к xy-фрагментам.
  • Верхняя граница верна. Вычислите наибольший размер tileSize, который приведет к (x + 1) (y + 1) фрагментам
  • Правильный ответ лежит где-то между верхней и нижней границами. Решите уравнение, чтобы определить правильные значения 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);
            }
        }
    }
}

Ответ 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 в коде.)
  • Loop, где я идет от 1 до включительно:
    • Постройте сетку квадратов, которая представляет собой количество фрагментов /i квадратов в ширину и я квадратов. (Закруглите все вверх. Это "прокладывает" недостающие плитки, например, используя 2 плитки на 2 плитки, когда ваше общее количество плиток равно 3.)
    • Сделайте эту сетку максимально возможной. (Рассчитайте это с использованием пропорций.) Определите размер одной плитки.
    • Используя этот размер, определите общую площадь, покрываемую фрагментами.
    • Убедитесь, что это лучшая общая область; если это так, сохраните квадратный размер
  • Вернуть этот квадратный размер

Это, вероятно, один из самых быстрых алгоритмов, перечисленных здесь, поскольку он вычисляет лучший квадратный размер в O (sqrt (n)) для 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)), поэтому этот обновленный метод не асимптотически быстрее первой попытки.


Некоторые обновления из потока комментариев

/*
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;
}

Ответ 6

Не могли бы вы рассказать о том, как вы определяете заполнение? Если я последую вашему описанию (большое, если), кажется, что многие из описанных вами случаев фактически не заполняют прямоугольник. Например, вы говорите, что 2 квадрата в прямоугольнике 100 * 100 будут 50 * 50. Если я правильно понимаю вашу конфигурацию, они будут помещены в "диагональ" этого прямоугольника. Но тогда в этом прямоугольнике было бы два "зазора" размером 50 * 50. Это не то, что я считаю "заполнением" прямоугольника. Вместо этого я поставил бы проблему как самый возможный размер для 2 (квадраты равного размера), чей ограничивающий прямоугольник будет 100 * 100 (при условии, что каждый квадрат должен был быть связан хотя бы с одним другим квадратом?).

Ключевым моментом здесь является то, что ваш прямоугольник, кажется, является ограничивающим прямоугольником и не заполнен.

Кроме того, можете ли вы написать функциональный интерфейс для этого расчета? Вам нужно сделать это для n возможных квадратов с учетом размеров ограничивающей рамки?

Ответ 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

в конце return l

Ответ 8

Я предполагаю, что квадраты не могут быть повернуты. Я уверен, что проблема очень сложная, если вам разрешено вращать их.

Итак, мы заполняем прямоугольник квадратами, начиная с левого верхнего угла. Затем мы помещаем квадраты справа от этого квадрата, пока не достигнем правой стороны прямоугольника, тогда мы сделаем то же самое со следующей строкой, пока не достигнем дна. Это похоже на написание текста на бумаге.

Соблюдайте, что никогда не будет ситуации, когда пространство остается справа и внизу. Если в обоих направлениях есть место, мы можем увеличить размер квадратов.

Предположим, что мы уже знаем, что 10 квадратов должны быть помещены в первую строку и что это идеально подходит для ширины. Тогда длина стороны width/10. Поэтому мы можем разместить квадраты m = height/sidelength в первом столбце. Эта формула могла бы сказать, что мы можем разместить 2.33 квадрата в первом столбце. Невозможно разместить 0,33 квадрата, мы можем разместить только 2 квадрата. Действительная формула m = floor(height/sidelength).

Не очень быстрый (но LOT быстрее, чем попытка каждой комбинации) алгоритм состоит в том, чтобы попытаться сначала поместить 1 квадрат в первую строку/столбец, а затем посмотреть, можем ли мы разместить достаточно прямоугольников в прямоугольнике. Если это не сработает, мы попробуем 2 квадрата в первой строке/столбце и т.д., Пока мы не сможем подобрать количество нужных фрагментов.

Я думаю, что существует O (1) алгоритм, если вам разрешено делать арифметику в O (1), но я до сих пор не понял.

Вот рубиновая версия этого алгоритма. Этот алгоритм 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 (# плит)).

Ответ 9

x = max(rectHeight/numberOfSquares, rectangleLength/numberOfSquares)

if x <= retangleHeight && x <= rectangleLength then
  squareSideLength = x
else
  squareSideLength = min(rectangleHeight, rectangleLength)

Ответ 10

Разделите большую сторону на количество плиток. Используйте меньшую сторону как размер плитки. Presto! # плиток.

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