Мій алгоритм, який витягує найбільшу скриньку, яку можна зробити з менших коробок, занадто повільний


9

Уявіть світ на основі кубів (наприклад, Minecraft, Trove або Cube World), де все складається з однакових розмірів кубів і всі куби одного типу .

Мета - представити світ з найменшою кількістю прямокутних коробок (шляхом злиття кубів, але зберігаючи опуклу форму (він же, прямокутна форма коробки)). Мій алгоритм в цьому досягає успіху, але його продуктивність занадто повільна за призначенням. Чи існують більш швидкі алгоритми?

Псевдо-C # -код для мого алгоритму в основному:

struct Coordinate { int x,y,z; }; //<-- integer based grid
HashSet<Coordinate> world; // <-- contains all the cubes

//width, height, and length represent how many cubes it spans
struct RectangularBox { Coordinate coord; int width,height,length; }

void Begin()
{
    List<RectangularBox> fewestBoxes = new List<RectangularBox>();
    while(world.Count > 0)
    {
         RectangularBox currentLargest = ExtractLargest();
         fewestBoxes.Add(currentLargest);
         world.RemoveRange(currentLargest.ContainedCubes());
    }
    //done; `fewestBoxes` contains the fewest rectangular boxes needed.
}

private RectangularBox ExtractLargest()
{
    RectangularBox largestBox = new RectangularBox();
    foreach (Coordinate origin in world)
    {
        RectangularBox box = FindMaximumSpan(origin);
        if (box.CalculateVolume() >= largestBox.CalculateVolume())
            largestBox = box;
    }
    return largestBox;
}

private RectangularBox FindMaximumSpan(Coordinate origin)
{
    int maxX, maxY,maxZ;
    while (world.Contains(origin.Offset(maxX, 0, 0))) maxX++;
    while (world.Contains(origin.Offset(0, maxY, 0))) maxY++;
    while (world.Contains(origin.Offset(0, 0, maxZ))) maxZ++;

    RectangularBox largestBox;
    for (int extentX = 0; extentX <= maxX; extentX++)
        for (int extentY = 0; extentY <= maxY; extentY++)
            for (int extentZ = 0; extentZ <= maxZ; extentZ++)
            {
                int lengthX = extentX + 1;
                int lengthY = extentY + 1;
                int lengthZ = extentZ + 1;
                if (BoxIsFilledWithCubes(origin, lengthX, lengthY, lengthZ))
                {
                    int totalVolume = lengthX * lengthY * lengthZ;
                    if (totalVolume >= largestBox.ComputeVolume())
                        largestBox = new RectangularBox(origin, lengthX, lengthY, lengthZ);
                }
                else
                    break;
            }
    return largestBox;
}

private bool BoxIsFilledWithCubes(Coordinate coord, 
    int lengthX, int lengthY, int lengthZ)
{
    for (int gX = 0; gX < lengthX; gX++)
        for (int gY = 0; gY < lengthY; gY++)
            for (int gZ = 0; gZ < lengthZ; gZ++)
                if (!world.Contains(coord.Offset(gX, gY, gZ)))
                    return false;
    return true;
}

По суті, для кожного блоку в світі він вперше знаходить, скільки суміжних блоків у кожному з трьох позитивних вимірів (+ X, + Y, + Z). А потім він якось заповнює цей об'єм і перевіряє, яка є найбільшою заливкою, яка не пропускає жодного блоку.


Оновлення: Оскільки я, мабуть, мав на увазі, що це стосується механізму візуалізації гри, я просто хочу уточнити, це не для двигуна візуалізації гри; це для файлового конвертера; немає графічного інтерфейсу.


2
Можливо, більше підходить для codereview.stackexchange.com
Rotem

4
@Rotem Можливо, але я насправді шукаю альтернативні алгоритми, а не огляд свого коду. Я надав свій код просто як звичка.
Містер Сміт

Звичайно, має сенс.
Ротем

Питання алгоритму більше підходять для сайтів SE, таких як інформатика ...
Bakuriu

Це також залежить від того, як часто ви викликаєте метод. Якщо ви називаєте це кожен кадр або лише тоді, коли блок змінюється. Зазвичай у цих іграх є шматки (певний розмір прямокутника, наприклад: 64x64x32 блоків), максимально кешовані значення та обчислюються лише на шматок. І тільки обчислюйте ці значення на видимих ​​блоках.
the_lotus

Відповіді:


6

Ви можете скористатися тим, що коли

 BoxIsFilledWithCubes(c,x,y,z)

повертає істину, тоді не потрібно перевіряти BoxIsFilledWithCubes(c,x+1,y,z)всі ці кубики в діапазоні координат "(c, x, y, z)" ще раз. Вам потрібно лише перевірити ці кубики з новою x-координатою c.x + (x+1). (Те саме стосується y+1або z+1). Більш загально, розділивши коробку на дві менші ящики (для яких ви вже можете знати, чи вони обидва заповнені кубиками, чи не обидва заповнені), ви можете застосувати тут метод розділення та перемоги, який стає швидшим, ніж ваш оригінальний підхід, коли ви кешуєте проміжні результати.

Для цього ви починаєте реалізувати BoxIsFilledWithCubes(c,x,y,z)рекурсивно, наприклад:

 bool BoxIsFilledWithCubes(coord,lx,ly,lz)
 {
     if(lx==0|| ly==0 || lz==0)
        return true;
     if(lx==1 && ly==1 && lz==1)
          return world.Contains(coord);
     if(lx>=ly && lx>=lz)  // if lx is the maximum of lx,ly,lz ....
         return BoxIsFilledWithCubes(coord,lx/2,ly,lz) 
             && BoxIsFilledWithCubes(coord.Offset(lx/2,0,0), lx-lx/2, ly, lz);
     else if(ly>=lz && ly>=lx)  
         // ... analogously when ly or lz is the maximum

 }

а потім скористайтеся пам'яттю (як це обговорюється тут ), щоб уникнути повторних дзвінків BoxIsFilledWithCubesз тими ж параметрами. Зверніть увагу, що вам доведеться очистити кеш пам’яті, коли ви застосуєте зміни до свого worldконтейнера, як, наприклад, від world.RemoveRange. Але я думаю, це зробить вашу програму швидшою.


5

Створіть в октодерево з листовим вузлом розміром аабб розміру вашої коробки. Під час обходу октрису ви можете дешево злити вузли. Повністю заповнені вузли тривіальні для злиття (новий ящик = батьківський aabb), тоді як для частково заповнених вузлів ви можете використовувати варіант вашого поточного алгоритму для перевірки здатності до злиття.


Те, що два вузли повністю заповнені, не означає, що їх слід об'єднати; це не крок до пошуку найбільшої коробки; якщо ви їх об’єднаєте, вам, швидше за все, доведеться розділити їх знову пізніше, коли буде знайдено найбільший ящик. Я не бачу, як у цьому сценарії допомагає octree.
Містер Сміт

Повні вузли можна об'єднати в тому сенсі, що всі 8 дітей можуть стати частиною одного більшого ящика. Цей більший ящик пізніше не доведеться розділяти, але він може бути об’єднаний. Ієрархія дозволяє швидко об'єднати безліч коробок, повністю заповнені вузли дозволяють піднятися на рівень без подальшого тестування.
Вільберт

1

Вам здається, що ви принаймні O (n ^ 2) (див. Велику нотацію O ), коли ви перебираєте всі поля світу у “Початок ()”, тоді для кожного поля ви перебираєте всі поля світу у ExtractLargest ( ). Таким чином, світ з 10 незв’язаними коробками займе в 4 рази довше, ніж світ з 5 непов'язаними коробками.

Тому вам потрібно обмежити кількість полів, на які має розглянути ExtractLargest (), щоб зробити це вам потрібно використовувати певний тип просторового пошуку , оскільки ви працюєте в 3d, вам може знадобитися 3d просторовий пошук. Однак спочатку почніть з розуміння 2d просторового пошуку.

Тоді подумайте, чи буде у вас колись багато вікон один над одним, якщо ні, то 2d просторовий пошук, який просто охоплює х, у може бути достатньо, щоб зменшити циклічність.

Octree / quadtree - це один із варіантів, але існує багато інших варіантів розділення простору ,

Але ви можете просто використовувати двовимірний масив списків ( просторовий індекс Grid ), де всі поля, що охоплюють точку (a, b), знаходяться в масиві [a, b] .list. Але найліпше це призведе до занадто великого масиву, то як щодо масиву [mod (a, 100), mob (b, 100)]. Все це залежить від того, якими є ваші дані .

(Я бачив, як рішення сітки дуже добре працює в реальному житті.)

Або зробіть те, що говорить Вільберт із октрису з вузлом листя, розміром якого розмір вашого коробки, але пізніше вам, ймовірно, доведеться знайти поле, на яке вказує миша користувача і т. Д., Ще раз випадок просторового пошуку.

( Ви повинні вирішити, чи просто ви намагаєтесь змусити це програмне забезпечення працювати, чи ви намагаєтесь зрозуміти, як бути кращим програмістом, і, отже, більше зацікавлені у навчанні, а потім швидке рішення. )

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.