Thuật toán của tôi trích xuất hộp lớn nhất có thể được tạo từ các hộp nhỏ hơn, quá chậm


9

Hãy tưởng tượng một thế giới dựa trên khối lập phương (như Minecraft, Trove hoặc Cube World) nơi mọi thứ được tạo thành từ các hình khối có kích thước giống hệt nhau và tất cả các hình khối đều cùng loại .

Mục tiêu là đại diện cho thế giới với số lượng hộp hình chữ nhật ít nhất (bằng cách hợp nhất các hình khối nhưng vẫn giữ hình dạng lồi (hay còn gọi là hình hộp hình chữ nhật)). Thuật toán của tôi thành công ở đây, nhưng hiệu suất của nó quá chậm so với mục đích của nó. Có thuật toán nhanh hơn?

Mã giả mã C # cho thuật toán của tôi về cơ bản là:

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

Về cơ bản, đối với mọi khối trên thế giới, trước tiên, người ta sẽ tìm thấy có bao nhiêu khối liên kết trong mỗi ba chiều dương (+ X, + Y, + Z). Và sau đó, nó sẽ lấp đầy khối lượng đó và kiểm tra khối lượng lớn nhất không thiếu bất kỳ khối nào.


Cập nhật: Vì dường như tôi đã ngụ ý rằng đây là công cụ kết xuất của trò chơi, tôi chỉ muốn làm rõ, đây không phải là công cụ kết xuất của trò chơi; nó dành cho trình chuyển đổi tập tin; không có GUI.


2
Có lẽ phù hợp hơn với codereview.stackexchange.com
Rotem

4
@Rotem Có lẽ, nhưng tôi thực sự đang tìm kiếm các thuật toán thay thế hơn là xem xét mã của tôi. Tôi đã cung cấp mã của mình như một thói quen.
Ông Smith

Chắc chắn, có ý nghĩa.
Rotem

Các câu hỏi về thuật toán phù hợp hơn với các trang SE như khoa học máy tính ...
Bakuriu 21/07/2015

Nó cũng phụ thuộc vào tần suất bạn gọi phương thức. Nếu bạn gọi nó là mọi khung hình hoặc chỉ khi một khối thay đổi. Các trò chơi này thường có các khối (hình chữ nhật có kích thước cụ thể, ví dụ: khối 64x64x32), giá trị bộ đệm càng nhiều càng tốt và chỉ tính trên mỗi khối. Và chỉ tính các giá trị này trên các khối nhìn thấy được.
the_lotus 21/07/2015

Câu trả lời:


6

Bạn có thể sử dụng thực tế là khi

 BoxIsFilledWithCubes(c,x,y,z)

trả về true, sau đó không cần thiết phải kiểm tra lại BoxIsFilledWithCubes(c,x+1,y,z)tất cả các hình khối đó trong phạm vi tọa độ "(c, x, y, z)". Bạn chỉ cần kiểm tra các hình khối đó với tọa độ x mới c.x + (x+1). (Tương tự là đúng cho y+1, hoặc z+1). Tổng quát hơn, bằng cách chia một hộp thành hai hộp nhỏ hơn (mà bạn có thể đã biết nếu cả hai đều chứa đầy hình khối hoặc không được lấp đầy), bạn có thể áp dụng kỹ thuật phân chia và chinh phục ở đây, nhanh hơn so với phương pháp ban đầu khi bạn lưu trữ các kết quả trung gian.

Để thực hiện điều này, bạn bắt đầu thực hiện BoxIsFilledWithCubes(c,x,y,z)đệ quy, ví dụ:

 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

 }

và sau đó sử dụng ghi nhớ (giống như được thảo luận ở đây ) để tránh mọi cuộc gọi lặp lại BoxIsFilledWithCubesvới cùng tham số. Lưu ý, bạn sẽ phải xóa bộ nhớ cache ghi nhớ khi bạn áp dụng thay đổi cho vùng worldchứa của mình , như bằng world.RemoveRange. Tuy nhiên tôi đoán điều này sẽ làm cho chương trình của bạn nhanh hơn.


5

Xây dựng một octree với kích thước aabb nút lá kích thước của hộp của bạn. Trong khi di chuyển qua quãng tám, bạn có thể hợp nhất các nút một cách rẻ tiền. Các nút được điền đầy đủ là tầm thường để hợp nhất (hộp mới = cha mẹ aabb), trong khi đối với các nút được điền một phần, bạn có thể sử dụng một biến thể của thuật toán hiện tại của mình để kiểm tra khả năng hợp nhất.


Hai nút được điền đầy đủ không có nghĩa là chúng nên được hợp nhất; đó không phải là một bước để tìm hộp lớn nhất; nếu bạn hợp nhất chúng, bạn có thể sẽ phải chia lại chúng vào một thời điểm sau khi tìm thấy hộp lớn nhất. Tôi không thấy một octree giúp như thế nào trong kịch bản này.
Ông Smith

Các nút đầy đủ có thể được hợp nhất theo nghĩa là tất cả 8 trẻ em có thể trở thành một phần của một hộp lớn hơn. Hộp lớn hơn này sẽ không phải chia ra sau này, nhưng nó có thể được hợp nhất. Hệ thống phân cấp cho phép bạn nhanh chóng hợp nhất nhiều hộp, các nút được điền hoàn toàn cho phép bạn nhảy lên một cấp độ mà không cần kiểm tra thêm.
Wilbert

1

Bạn dường như ít nhất là O (n ^ 2) (xem ký hiệu O lớn ) khi bạn lặp qua tất cả các ô trên thế giới trong Trò chơi Bắt đầu (), sau đó, với mỗi hộp, bạn lặp qua tất cả các ô trên thế giới trong ExtractLargest ( ). Vì vậy, một thế giới có 10 hộp không liên quan sẽ mất 4 lần so với một thế giới có 5 hộp không liên quan.

Do đó, bạn cần giới hạn số lượng hộp mà ExtractLargest () phải xem, để làm điều đó bạn cần sử dụng một số loại tìm kiếm không gian , khi bạn đang làm việc trong 3d, bạn có thể cần tìm kiếm không gian 3d. Tuy nhiên trước tiên hãy bắt đầu bằng cách hiểu tìm kiếm không gian 2d.

Sau đó, xem xét nếu bạn sẽ có nhiều hộp chồng lên nhau, nếu không thì một tìm kiếm không gian 2d chỉ bao gồm x, y có thể đủ để cắt giảm vòng lặp của bạn.

Octree / quadtree là một tùy chọn, nhưng có nhiều tùy chọn khác để phân vùng không gian ,

Nhưng bạn có thể chỉ cần sử dụng một mảng danh sách 2 chiều ( Chỉ mục không gian lưới ), trong đó tất cả các hộp bao gồm điểm (a, b) nằm trong mảng [a, b] .list. Nhưng hầu hết lickly sẽ dẫn đến một mảng quá lớn, vậy còn mảng [mod (a, 100), mob (b, 100)]. Tất cả điều này phụ thuộc vào dữ liệu của bạn là như thế nào .

(Tôi đã thấy giải pháp lưới hoạt động rất tốt trong cuộc sống thực.)

Hoặc làm những gì Wilbert nói với một octree có nút lá kích thước aabb có kích thước của hộp của bạn, nhưng sau này bạn có thể phải tìm hộp mà chuột của người dùng đang chỉ vào, một lần nữa, một trường hợp tìm kiếm không gian.

( Bạn phải quyết định xem bạn chỉ đang cố gắng để phần mềm này hoạt động hay nếu bạn đang cố gắng hiểu làm thế nào để trở thành một lập trình viên tốt hơn và do đó quan tâm hơn đến việc học thì hãy giải quyết nhanh chóng. )

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.