Максимальный размер квадрата для неизвестного числа внутри прямоугольника - PullRequest
14 голосов
/ 15 мая 2009

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

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

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

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


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

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

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

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

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

Ответы [ 10 ]

5 голосов
/ 16 мая 2009

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

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

5 голосов
/ 15 мая 2009

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

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

4 голосов
/ 01 ноября 2009

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

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

На основании этих границ возможны три варианта:

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

Я протестировал эту функцию для всех целочисленных значений ширины, высоты и тайлаCount от 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 голосов
/ 18 мая 2009

Мне удалось найти «относительно» оптимальное решение. Частично на основе псевдокодового ответа Зака.

        //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 для моего примера. Спасибо за всю разнообразную информацию.

1 голос
/ 16 мая 2009

Следующая функция вычисляет плитку максимального размера для данной информации.

Если тот факт, что он написан на 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 до с включительно:
    • Построить сетку из квадратов, которая составляет количество плиток / i квадратов в ширину и 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х4 и 6х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;
}
0 голосов
/ 17 мая 2009

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

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

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

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

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

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

Вот Ruby-версия этого алгоритма. Этот алгоритм O (sqrt (число плиток)), если прямоугольник не очень тонкий.

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 (количество плиток)).

0 голосов
/ 16 мая 2009
x = max(rectHeight/numberOfSquares, rectangleLength/numberOfSquares)

if x <= retangleHeight && x <= rectangleLength then
  squareSideLength = x
else
  squareSideLength = min(rectangleHeight, rectangleLength)
0 голосов
/ 15 мая 2009

Заданные значения:

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

в конце возврата л

0 голосов
/ 15 мая 2009

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

Rectagle = 200 x 10
Each tile is 10 x 10 (length of shorter side)
200/10 = 20 (number of tiles needed)
0 голосов
/ 15 мая 2009

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...