可以使用哪种算法以相当理想的方式将不同大小的矩形打包为可能的最小矩形?


273

我有一堆矩形对象,我需要将它们打包到尽可能小的空间中(此空间的尺寸应为2的幂)。

我知道各种各样的打包算法,这些算法会将项目尽可能地打包到给定的空间中,但是在这种情况下,我需要该算法来计算出该空间也应该有多大。

例如,说我有以下矩形

  • 128 * 32
  • 128 * 64
  • 64 * 32
  • 64 * 32

它们可以包装成128 * 128的空间

 _________________
| 128 * 32 |
| ________________ |
| 128 * 64 |
| |
| |
| ________________ |
| 64 * 32 | 64 * 32 |
| _______ | ________ |

但是,如果同时有160 * 32和64 * 64,则需要256 * 128的空间

 ________________________________
| 128 * 32 | 64 * 64 | 64 * 32 |
| ________________ | | _______ |
| 128 * 64 | | 64 * 32 |
| | _______ | _______ |
| | |
| ________________ | ___ |
| 160 * 32 | |
| ____________________ | ___________ |

有哪些算法可以打包一堆矩形并确定容器所需的尺寸(2的幂,并且在每个尺寸的给定最大尺寸内)?


6
第二种解决方案不是最优的吗?难道不是224乘128?
Mantas Vidutis

“此空间的大小应为2的幂”因此没有区别,因为这是过去的,因为我不能假设底层硬件无条件地支持2的幂。
Fire Lancer 2010年

2
无论如何,它最终使算法更简单(尝试将其全部放入32x32中,如果nto,然后尝试64x32、64x64、128x64等):)
Fire Lancer 2010年


我在这里放置了一种蛮力解决方案stackoverflow.com/a/47698424/1641247
Sean

Answers:


67

快速而肮脏的首过解决方案始终是一个很好的开始,如果没有其他比较的话。

贪婪的放置方式从大到小。

将剩余的最大矩形放入您的打包区域。如果无法将其放置在任何地方,请将其放置在尽可能减少包装区域的地方。重复直到完成最小的矩形。

它根本不是完美的,但是很简单,并且是一个不错的基准。它仍然可以完美地包装您的原始示例,并为第二个示例提供相同的答案。


1
我只是在纸上玩类似的游戏,在大多数情况下,即使不旋转矩形或任何东西,现在看起来也都非常理想
Fire Lancer

1
我实现了它,并通过它运行了一堆测试数据,似乎做得很好,只留下了一些浪费的数据。现在,我只需要重写我的实现,以比通过可用空间对每个rect进行线性搜索更有效,检查每个像素是否已阻塞该点(针对所有现有rects)...
Fire Lancer,2009年


2
我很难想像这可以达到最佳效果。因此,我已经对其进行了编码(具有正方形),并且效果很好。这是一个演示动画:imgur.com/ISjxuOR
Attila

@JimBalter明智的方形空间……可能……在速度和可扩展性方面?并不是的?
Arek Bal '18

86

有关解决方案的概述,请参见ARC项目上的此页面,在实现复杂性/时间和最佳性之间需要权衡取舍,但是有多种算法可供选择。

这是算法的摘录:

  1. 首次拟合的递减高度(FFDH)算法
    FFDH在适合R的第一层上打包下一个项目R(以非递增高度)。如果没有任何级别可以容纳R,则会创建一个新级别。
    FFDH的时间复杂度:O(n·log n)。
    近似比:FFDH(I)<=(17/10)·OPT(I)+1;渐近边界为17/10。

  2. 下一身减低高度(NFDH)算法
    如果R适合,则NFDH在当前水平上打包下一个R项(不增加高度)。否则,将“关闭”当前级别并创建一个新级别。
    时间复杂度:O(n·log n)。
    近似比:NFDH(I)<= 2·OPT(I)+1; 2的渐近边界是紧的。

  3. 最佳拟合递减高度(BFDH)算法
    BFDH在可容纳R的那些项目中,将水平水平上的下一个项目R(在非递增高度中)打包,其中剩余水平空间最小。如果没有任何级别可以容纳R,则会创建一个新级别。

  4. 左下(BL)算法
    BL一阶项的宽度不增加。BL会将下一个项目包装到最适合的底部,然后尽可能地向左包装,而不与任何包装的项目重叠。注意,BL不是面向级别的打包算法。
    时间复杂度:O(n ^ 2)。
    近似比:BL(I)<= 3·OPT(I)。

  5. 贝克的上下(UD)算法
    UD使用BL和NFDH的组合。条带和项目的宽度被标准化,以使条带具有单位宽度。UD以不增加宽度的顺序对项目进行排序,然后将其分为五组,每组的宽度在(1/2,1],(1 / 3,1 / 2],(1 / 4,1 / 3)范围内],(1 / 5,1 / 4],(0,1 / 5]。该条带也分为五个区域R1,...,R5。基本上,一些宽度在(1 / i + 1,1 / i],对于1 <= i <= 4,由BL填充到区域Ri。由于BL在带材的右侧从顶部到底部留有宽度递增的空间,因此UD首先利用了这一优势从顶部到底部j = 1,...,4(按顺序)将项目包装到Rj。如果没有这样的空间,则通过BL将项目包装到Ri。最后,大小最大为1/5的项目通过(广义)NFDH算法将它们打包到R1,...,R4中的空间。
    近似比:UD(I)<=(5/4)·OPT(I)+(53/8)H,其中H是物品的最大高度;5/4的渐近边界是紧密的。

  6. 逆向拟合(RF)算法
    RF还可以对条带和物品的宽度进行归一化处理,以使条带具有单位宽度。RF首先堆叠宽度大于1/2的所有项目。其余物品将按不增加的高度进行分类,并且将被包装在大于1/2的物品达到的高度H0以上。然后RF重复以下过程。粗略地说,RF从左到右包装物品,其底部沿高度H0线直到没有更多空间。然后从右到左,从上到下(称为反向包装)包装物品,直到总宽度至少为1/2。然后将反向级别下拉,直到(至少)其中一个触摸下面的某个项目。下拉菜单以某种方式重复出现。
    近似比:RF(I)<= 2·OPT(I)。

  7. Steinberg算法
    Steinberg算法(在本文中用M表示)估计打包所有项目所需的高度H的上限,从而证明输入项目可以打包为宽度W和高度H的矩形。定义七个过程(具有七个条件),每个过程将一个问题分为两个较小的过程,然后递归解决它们。已经表明,任何可解决的问题都满足七个条件之一。
    近似比:M(I)<= 2·OPT(I)。

  8. Split-Fit算法(SF)SF将项目分为两组,L1的宽度大于1/2,L2的最大值为1/2。L1的所有物品首先由FFDH包装。然后排列它们,以使所有宽度大于2/3的项目都低于宽度最大为2/3的项目。这将创建一个宽度为1/3的矩形R。然后,使用FFDH将L2中的其余项目打包到R,并在L1之上打包剩余的空间。在R中创建的级别被认为低于在L1包装之上创建的级别。
    近似比:SF(I)<=(3/2)·OPT(I)+ 2; 3/2的渐近边界是紧密的。

  9. Sleator的算法
    Sleter的算法包括四个步骤:

    1. 所有宽度大于1/2的项目都在条带的底部彼此堆叠。假设h0是最终包装的高度,所有后续包装将在h0以上进行。

    2. 剩余的物品按不增加的高度排序。沿着高度h0从左到右包装了一定等级的物品(以不增加的高度顺序)。

    3. 然后在中间画一条垂直线,将条带切成相等的两半(请注意,这条线可能会切割右半部分包装的物品)。绘制两条长度为一半的水平线段,一条横越左半段(称为左基线),一条横越右半段(称为右基线),其长度应尽可能低,以使两条线不会穿过任何项目。

    4. 选择较低高度的左或右基线,并将一定水平的物品包装到条带的相应一半中,直到下一个物品太宽为止。

    形成一个新的基线,并在较低的基线上重复步骤(4),直到包装完所有物品为止。
    时间复杂度:O(n·log n)。
    Sleator算法的逼近率为2.5,这很严格。


6
所有这些都需要知道空间的宽度。
Quantum7年

1
@ Quantum7可能不太重要,因为OP要求边为2的幂,因此我们可以尝试一堆具有足够面积的尺寸。
Ciro Santilli郝海东冠状病六四事件法轮功

19

看一下包装问题。我认为您属于“ 2D装箱”。您应该能够从解决该问题和其他包装问题中学到很多东西。

另请参见:将矩形图像数据打包为正方形纹理。


这是优化矩形打包算法的另一个很好的例子:codeproject.com/Articles/210979/…–
Anderson Green

在以下网站中也提到了问题:en.wikipedia.org/wiki/…值得注意的是,这将垃圾箱包装限制为一个未知大小的垃圾箱,我想知道那里是否仍然是NP完整的。
Ciro Santilli郝海东冠状病六四事件法轮功

17

关于这个问题有大量文献。贪婪的启发式方法是将第一个可用位置中从最大区域到最小矩形的矩形放置在朝向容器底部和左侧的位置。考虑一下重力,将所有物品拉到左下角。有关此Google“ Chazelle左下包装”的说明。

为了获得最佳解决方案,最新技术可以在几秒钟内包装20多个矩形。Huang有一种算法,将找到最小的封闭边界框的问题与确定一组矩形是否适合特定大小的边界框的问题区分开来。您给他的程序提供了一组矩形,它告诉您包装它们所需的最小封闭边界框。

对于您的情况,您的外循环应从可能的最小边界框向上迭代(宽度和高度依次乘以2的幂)。对于每个这些边界框,进行测试以查看是否可以找到矩形的包装。您将得到一堆“否”的答案,直到第一个“是”的答案为止,这肯定是最佳解决方案。

对于您的算法的内循环-对特定大小的边界框回答“是”或“否”的那一环,我将查找Huang参考并仅实现他的算法。他在基本算法的基础上进行了很多优化,但是您实际上只需要基本的肉和土豆。由于您要处理旋转,因此在搜索过程中的每个分支点上,只需尝试旋转并在两个旋转都无法解决时回溯即可。


9

我相当确定这是一个NP难题,因此,为了获得最佳解决方案,您必须实现一个尝试所有可能组合的回溯算法。

好消息是,由于需要在有限的2D空间中包装2D矩形,因此您可以尽早修剪掉许多可能性,因此可能并不坏。


3
您可能是指NP完全。
starblue

7
好吧,如果它是NP完整的,那么就很容易解决,只需解决旅行推销员的同等实例,然后您就可以解决。但是要证明这一点并不重要,因为NP完全问题是决策问题(您得到的是/否答案),并且具有多项式时间验证算法。问题“是否有矩形a,b,c ...的排列占用的面积小于256 * 128可能是NP完整的
Eclipse

2
@Eclipse是正确的。来自jair.org/media/3735/live-3735-6794-jair.pdf “优化问题是NP困难的,而决定一组矩形是否可以装在给定的边界框中的问题是NP完整的,通过减少垃圾箱包装(Korf,2003年)。” 但是,请注意,OP要求“一种相当理想的方式”,并且在P中有针对“相当”的足够广泛定义的解决方案。
Jim Balter 2015年

我也怀疑NP硬度,但是我们需要参考/证明。
Ciro Santilli郝海东冠状病六四事件法轮功

2
神圣线死灵,蝙蝠侠。这是一个包装问题,并且已经被证明最多是NP难题:en.wikipedia.org/wiki/Packing_problems
Blindy

2

您需要的是 https://github.com/nothings/stb/blob/master/stb_rect_pack.h

样品:

stbrp_context context;

struct stbrp_rect rects[100];

for (int i=0; i< 100; i++)
{
    rects[i].id = i;
    rects[i].w = 100+i;
    rects[i].h = 100+i;
    rects[i].x = 0;
    rects[i].y = 0;
    rects[i].was_packed = 0;
}

int rectsLength = sizeof(rects)/sizeof(rects[0]);

int nodeCount = 4096*2;
struct stbrp_node nodes[nodeCount];


stbrp_init_target(&context, 4096, 4096, nodes, nodeCount);
stbrp_pack_rects(&context, rects, rectsLength);

for (int i=0; i< 100; i++)
{
    printf("rect %i (%hu,%hu) was_packed=%i\n", rects[i].id, rects[i].x, rects[i].y, rects[i].was_packed);
}

1

一个通用的解决方案是不平凡的(数学上说完全不可能实现)
通常,人们使用遗传算法来尝试可能的组合,但是您可以通过将最大的形状放在第一位然后在不同的位置尝试最大的组合来做得很好。第二大,依此类推。

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.