我的算法无法从较小的盒子中提取出最大的盒子,但算法太慢


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)中的每个区块中有多少个毗连区块。然后,用某种方法填充该体积,并检查哪个是最大的填充而不会丢失任何块。


更新:由于我似乎暗示这是针对游戏的渲染引擎,因此我只想澄清一下,这不是针对游戏的渲染引擎。它用于文件转换器;没有GUI。


2
也许更适合codereview.stackexchange.com
Rotem

4
@Rotem也许,但是我实际上是在寻找替代算法,而不是审查我的代码。我已经提供了我的代码,只是一种习惯。
Smith先生

当然可以。
Rotem

算法问题更适合SE网站,例如计算机科学...
Bakuriu

它还取决于您调用该方法的频率。如果您每隔一帧或仅在块更改时调用它。这些游戏通常具有块(特定大小的矩形,例如:64x64x32块),尽可能多地缓存值,并且仅按块进行计算。并且仅在可见块上计算这些值。
the_lotus

Answers:


6

您可以利用以下事实:

 BoxIsFilledWithCubes(c,x,y,z)

返回true,则无需再次检入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容器应用更改时,您必须清除备注缓存,例如by world.RemoveRange。尽管如此,我想这将使您的程序更快。


5

建立一个八叉树,其叶子节点的大小等于盒子的大小。遍历八叉树时,您可以廉价地合并节点。完全填充的节点很难合并(新框=父aabb),而对于部分填充的节点,可以使用当前算法的变体检查合并能力。


两个节点完全填充并不意味着应该合并它们。这不是寻找最大盒子的一步;如果合并它们,则一旦找到最大的框,就可能不得不在以后再次拆分它们。在这种情况下,八叉树没有帮助。
Smith先生

整个节点可以合并,因为所有8个子节点都可以成为单个较大盒子的一部分。稍后不必拆分这个较大的框,但是可以将其合并。层次结构使您可以快速合并很多盒子,完全填充的节点可以使您跳到一个级别而无需进一步测试。
威尔伯特

1

当您在“ Begin()”中循环遍历世界上的所有框时,您似乎至少是O(n ^ 2)(请参见O表示),然后对于每个框,在ExtractLargest( )。因此,具有10个无关盒子的世界将比具有5个无关盒子的世界花费4倍的时间。

因此,您需要限制ExtractLargest()必须查看的框的数量,为此您需要使用某种类型的空间搜索,因为在3d中工作,可能需要3d空间搜索。但是,首先要从了解2d空间搜索开始。

然后考虑一下您是否会在彼此上方有很多框,如果没有,那么仅覆盖x,y的2d空间搜索可能足以减少循环。

八叉树 / 四叉树是一种选择,但是对于空间分区,还有许多其他选择,

但是您也许可以仅使用列表的二维数组(网格空间索引),其中覆盖点(a,b)的所有框都在array [a,b] .list中。但是最令人高兴的是,这将导致数组过大,那么array [mod(a,100),mob(b,100)]。list呢? 这一切都取决于您的数据是什么样的

(我已经看到网格解决方案在现实生活中效果很好。)

或者用威尔伯所说的八叉树(其叶子节点的大小等于盒子的大小)来做,但后来您可能不得不找到用户的鼠标指向的盒子等,这再次是空间搜索的情况。

您必须决定是否只是想使该软件运行,还是要了解如何成为一个更好的程序员,从而对学习和快速解决方案更感兴趣。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.