什么算法可用于以相当优化的方式将不同大小的矩形打包成最小的矩形?
Posted
技术标签:
【中文标题】什么算法可用于以相当优化的方式将不同大小的矩形打包成最小的矩形?【英文标题】:What algorithm can be used for packing rectangles of different sizes into the smallest rectangle possible in a fairly optimal way? 【发布时间】:2010-11-15 20:25:17 【问题描述】:我有一堆矩形物体,我需要将它们打包到尽可能小的空间中(这个空间的尺寸应该是 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 的幂,并且在每个维度的给定最大大小内)?
【问题讨论】:
第二种方案不是最优的吗?不应该是 128 乘 224 吗? "这个空间的尺寸应该是 2 的幂" 所以这没有区别,因为这是/现在我不能假设底层硬件无条件地支持非 2 的幂。 无论如何它最终使算法更简单(尝试将其全部放入 32x32,如果 nto 则尝试 64x32,然后尝试 64x64、128x64 等):) 见jair.org/media/3735/live-3735-6794-jair.pdf 我在这里放了一种蛮力解决方案***.com/a/47698424/1641247 【参考方案1】:我相当肯定这是an NP-hard problem,因此,为了获得最佳解决方案,您必须实施回溯算法来尝试所有可能的组合。
好消息是,由于需要在有限的 2D 空间中打包 2D 矩形,因此您可以尽早修剪很多可能性,因此可能还不错。
【讨论】:
你的意思可能是 NP-complete。 好吧,如果它是 NP 完全的,那么它很容易解决,只需解决旅行商的等价实例,就可以了。但是表明提出的问题是微不足道的,因为 NP 完全问题是决策问题(你会得到是/否的答案),并且有一个多项式时间验证算法。问题“是否存在面积小于 256*128 的矩形 a、b、c... 的排列可能是 NP 完全的。 @Eclipse 是正确的。来自jair.org/media/3735/live-3735-6794-jair.pdf“优化问题是 NP-hard,而通过减少 bin-packing(Korf,2003)来决定一组矩形是否可以打包在给定的边界框中的问题是 NP 完全的。”但是,请注意,OP 要求“一种相当优化的方式”,并且在 P 中有解决方案,用于“相当”的足够广泛的定义。 我也怀疑 NP 硬度,但我们需要参考/证明。 圣线死灵,蝙蝠侠。这是一个打包问题,并且已经证明它充其量是 NP-hard:en.wikipedia.org/wiki/Packing_problems【参考方案2】:看看packing problems。我认为您的属于“二维装箱”。您应该能够从该问题和其他包装问题的解决方案中学到很多东西。
另见:Packing rectangular image data into a square texture.
【讨论】:
这是另一个优化矩形打包算法的好例子:codeproject.com/Articles/210979/… 问题也提到:en.wikipedia.org/wiki/… 值得注意的是,这将 bin 打包限制在一个未知大小的 bin 中,我想知道它是否仍然是 NP-complete。【参考方案3】:一个通用的解决方案是不平凡的(数学说明完全不可能) 通常人们使用遗传算法来尝试可能的组合,但您可以通过将最大的形状放在最前面,然后尝试不同的位置以获得下一个最大的形状,等等。
【讨论】:
【参考方案4】:快速而肮脏的第一次通过解决方案总是一个很好的开始,作为比较,如果没有别的。
从大到小贪婪摆放。
将剩余的最大矩形放入打包区域。如果它不适合任何地方,请将其放置在尽可能少地扩展背包区域的地方。重复直到你完成最小的矩形。
它一点也不完美,但它很简单,而且是一个不错的基线。它仍然会完美地打包您的原始示例,并为您提供第二个相同的答案。
【讨论】:
我只是在一张纸上玩类似的东西,现在在大多数情况下看起来相当理想,即使没有旋转矩形或任何东西 我实现了它并通过它运行了一堆测试数据,似乎做得很好,只是留下了一点浪费的数据。现在我只需要重写我的实现,使其比通过可用空间对每个矩形进行线性搜索更有效,检查每个像素是否被阻塞(针对所有现有矩形)... 最优解在jair.org/media/3735/live-3735-6794-jair.pdf中给出 我很难想象这会如何优化。所以我对它进行了编码(方形),结果很好。这是一个演示动画:imgur.com/ISjxuOR @JimBalter 方形空间明智......可能......在速度和可扩展性方面?不是吗?【参考方案5】:关于这个问题有大量文献。一个好的贪心启发式方法是将矩形从最大区域到最小区域放置在容器底部和左侧的第一个可用位置。想象一下重力将所有物品拉到左下角。有关此谷歌“Chazelle 左下包装”的描述。
为了获得最佳解决方案,最先进的技术可以在几秒钟内打包 20 多个矩形。 Huang 有一个algorithm,它将寻找最小包围框的问题与决定一组矩形是否可以放入特定大小的边界框的问题分开。你给他的程序一组矩形,它会告诉你打包它们所需的最小封闭边界框。
对于您的情况,您的外部循环应从可能的最小边界框向上迭代(宽度和高度依次增加 2 的幂)。对于这些边界框中的每一个,测试您是否可以找到适合您的矩形的包装。你会得到一堆“否”的答案,直到第一个“是”的答案,这将保证是最优解。
对于算法的内部循环——对特定大小的边界框回答“是”或“否”的循环,我会查找 Huang 的参考资料并实施他的算法。他在基本算法的基础上做了很多优化,但你真的只需要基本的肉和土豆。由于您要处理旋转,因此在搜索过程中的每个分支点,只需在两种旋转都没有解决方案时尝试旋转和回溯。
【讨论】:
【参考方案6】:有关解决方案的调查,请参阅 this page on the ARC project,在实现复杂性/时间和最优性之间进行权衡,但有多种算法可供选择。
以下是算法的摘录:
首次拟合递减高度 (FFDH) 算法 FFDH 在 R 适合的第一层包装下一个项目 R(在非增加的高度)。如果没有级别可以容纳 R,则会创建一个新级别。 FFDH的时间复杂度:O(n·log n)。 近似比:FFDH(I)
下一次拟合递减高度 (NFDH) 算法 如果 R 适合,NFDH 会在当前级别上打包下一个项目 R(非增加高度)。否则,当前关卡将“关闭”并创建一个新关卡。 时间复杂度:O(n·log n)。 近似比:NFDH(I)
最佳拟合递减高度 (BFDH) 算法 BFDH 在能够容纳 R 的项目中,将水平上的下一个项目 R(非递增高度)打包,其中剩余水平空间最小。如果没有级别可以容纳 R,则创建一个新级别。
左下 (BL) 算法 BL 不增加宽度的第一个订单项目。 BL 将下一个项目尽可能靠近底部包装,然后尽可能靠近左侧而不与任何已包装的项目重叠。请注意,BL 不是面向级别的打包算法。 时间复杂度:O(n^2)。 近似比:BL(I)
贝克的上下 (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 近似比:UD(I)
反向拟合 (RF) 算法 RF 还规范化条带和项目的宽度,使条带具有单位宽度。 RF首先堆叠所有宽度大于1/2的物品。其余物品按非递增高度排序,大于 1/2 的物品将在高度 H0 以上进行包装。然后RF重复以下过程。粗略地说,RF 从左到右包装物品,底部沿着高度 H0 线,直到没有更多空间。然后从右到左,从上到下(称为反向层)打包物品,直到总宽度至少为 1/2。然后反向水平下降,直到(至少)其中一个触及下面的某个项目。下拉菜单以某种方式重复。 近似比:RF(I)
斯坦伯格算法 Steinberg 的算法,在论文中表示为 M,估计了打包所有项目所需的高度 H 的上限,从而证明输入项目可以打包成一个宽度为 W 和高度为 H 的矩形。然后他们定义了七个程序(有七个条件),每个程序将一个问题分成两个较小的问题并递归解决它们。已经证明,任何可处理的问题都满足七个条件之一。 近似比:M(I)
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)
Sleator 算法 Sleater 算法包括四个步骤:
宽度大于 1/2 的所有物品在条带底部相互叠放。假设 h0 是最终打包的高度,所有后续打包都将发生在 h0 以上。
剩余项目按非递增高度排序。沿着高度 h0 的线从左到右包装一个级别的项目(以非递增的高度顺序)。
然后在中间画一条垂直线,将条带切成相等的两半(注意这条线可能会切开部分包装在右半部分的物品)。绘制两条长度为一半的水平线段,一条穿过左半部(称为左基线),另一条穿过右半部(称为右基线),尽可能低,使两条线不与任何项目相交。
选择高度较低的左侧或右侧基线,将一层物品装入条带的相应一半,直到下一个物品太宽。
形成一个新的基线,并在较低的基线上重复步骤 (4),直到所有项目都被打包。 时间复杂度:O(n·log n)。 Sleator算法的逼近比是2.5,比较严格。
【讨论】:
所有这些都需要知道空间的宽度。 @Quantum7 可能不太重要,因为 OP 要求边是 2 的幂,所以我们可以尝试一堆具有足够面积的维度。【参考方案7】:您需要的是 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);
【讨论】:
以上是关于什么算法可用于以相当优化的方式将不同大小的矩形打包成最小的矩形?的主要内容,如果未能解决你的问题,请参考以下文章