1、背景
给定一个固定长宽高,固定容积的箱子和若干个不同固定尺寸的物件,能不能快速找到一个办法,能将这些物件通过进行不同的排列组合装填到箱子里,使这个箱子的填充率达到100%。
这个问题在计算机学界被称之为装箱问题。
装箱问题的应用范畴十分广泛。如物流运输,材料加工,布料剪裁,计算机中任务调度等都会大量应用到。而当今解决装箱问题,一直都是采用近似算法,
1-2、NP-Hard问题
装箱问题,因为涉及到不同物体之间的排列组合,所以一直被认为是一个NP-Hard问题。所谓的NP难问题,是指不能在多项式时间内解决的问题。简单来说,也就是不能精确估计到求解该问题所需要的时间。从算法与数据结构理论中,都知道大部分熟知的一般算法,如用于排序的冒泡法,快速排序法,遍历树的深度遍历法,广度遍历法,都可以算出其时间复杂度O。一般用于求解NP难问题的算法都是没有办法给出其准确的时间复杂度公式。如果一个算法不能估计其何时退出,对于计算机而言是一个灾难(等同于写了一个死循环)。
虽然NP-Hard问题无法估计其时间复杂度,但不代表不能求解NP-Hard问题。只要给NP-Hard问题加上特定的约束或退出条件,算法还是可以正常退出的。例如象棋,象棋中当出现势均力敌之势时,对奕双手可以走出无穷无尽的棋路来保持这个局势。也就是说如果两个AI对奕,假设两个AI的策略都是保守的话,两个AI很可能会陷入死循环。要让他们正常退出的话,只能增加一些约束,例如在一定步数下,棋子数较多的为胜。
所以求解NP-Hard问题的关键是选择求解策略和退出条件。像象棋是一个经典的NP难的问题,如果设计一个象棋AI,它是以尽可能的吃掉对方的棋子,来获得最终胜利,那么这个AI就是一个冒进型AI,在对奕的过程中(相当于求解象棋问题),它就会选择很多激进的做法。结果就是很快就赢,或者很快就输。
回到装箱问题,过去很多工业上应用的就是这样一个激进的策略(又叫贪心算法),如尽可能地往箱子里填入尽可能多的同类商品,但实际上,这样的策略未必是最优的。就像象棋中一个棋手拼命地想吃到对手的棋子,但事实上,吃掉最多棋子的未必就能赢。采取贪心算法,算法的效率的确是比较高,但质量不一定好。这也是一直以来装箱问题不能得到很好解决的原因
1-3、求解NP难问题的方法
装箱问题的本质是在特定的条件约束(不同数量的商品和包材)下,如何达到填充率最大(或者是空置率最低)的最优化问题。解最优化问题,有两种方案:
1、对问题进行精确建模,使问题的数学模型能找到对应的数学工具来解决。例如,求解多目标最优化问题(如旅行问题)可以使用层次分析法进行求解。对于一般化的可建模成连续可导函数的问题,更可以使用像共轭梯度法,最速下降法等高效算法。(相关的专业知识请参看最优化理论的相关书籍)
2、采用启发式搜索算法。如爬山法,模拟退火算法,遗传算法,蚁群算法,粒子群算法
这两种方案各有优缺点。
第1种方案的优点在于可快速求解,是求救效率最高的方案。难点在需要对问题进行高度的数学抽象,需要较深厚的数学功力。而且,即使能对问题进行准确建模,解决模型所需要的数学工具是否完备也是难题之一。
第2种方案的优点在于通用性强,不需要特别复杂的数学模型。但缺点是收敛速度低,且容易陷入局部最优解。
2 新解决方案
贪心法由于效率高,速度快,的确在实际应用上有其重要的作用。但贪心法也有其弱点,例如求解精度低,有时求解出来的结果,还不如人用肉眼心算。所以贪心法只是启发式算法中比较原始的方案。
新方案将使用基于成熟的数学模型(有严格的数学证明与推导,以及成熟的数学工具)和机器学习算法(遗传算法)来实现一个求解策略更优,精度更高的切箱算法方案。
2-1 基于丢番图方程(不定方程)的求解策略
2-1-1 数学推导与证明
引理:给定一个箱子(a,b,c),体积为V,和n个商品(a1,b1,c1),体积为V1,个数为x1,(a2,b2,c2),体积为V2,个数为x2,.......(xn,yn,zn),体积为V3,个数为xn,,如果这个箱子刚好能被塞满,那必定有
v1x1+v2x2+v3x3+....+vnxn=V,而且各个商品体积的最大公约数G=(V1,V2......Vn)刚好能整除V,数学上记作G|V,下文同。
实际上我们对形如a1x1+a2x2+a3x3+...+anxn=C的方程,我们有一个统一的称呼,叫做丢番图方程(以希腊数学家丢番图命名,又叫不定方程)。
关于一次不定方程的研究已经十分成熟,关于不等方程的一些结论和解法请参考数论方面的资料,这里不作赘述。
这个引理的结论部分在数论中已有证明。那么这个引理有什么作用呢?其实这个结论给出了一个快速判断这个箱子的填充率能否达到100%的条件。
举例,已有大箱子的长宽高为B(6,8,3),小商品A(2,3,5),B(3,5,7),通过这个引理,可以快速地得到结论:只用这两个小商品进行填充,这个大箱子的填充率不能达到100%。
原因很简单,因为小商品的A,B体积的最大公约数为15,15显然不能整除6*8*3。也就是说丢番图方程30X+105Y=144无整数解
用在装箱问题上,引理1的用处在于能从包材中找到一些箱子,有望能一次把这个箱子塞满。
沿用原来的例子,如果包材中有两个不同的箱子B1(3,5,9),B2(6,8,3),虽然B2的体积(144)比B1(135)的体积要大,但通过引理1的检测,算法会更趋向用B1,而不是B2。
现在第二个问题来了:满足了不定方程有解,是否就相当于证明了箱子一定能被塞满呢?
答案是否定的。请再看下面例子
箱子B为(5,1,54),商品A为(6,5,2),商品B为(3,5,7),丢番图方程60X+105Y=270有解,特解为X=1,Y=2。通解为X=1+7n,Y=2-4n。
因为这个箱子有一个最短边1,1 < 2 < 3< 5 < 6 < 7 。显然这个箱子不仅不能用这两个商品填满,而且还装不下任何一个商品。
显然,满足了体积的不定方程并不代表能得到问题的真实解。
为了逐步获得问题的真正解,下面将引入面积的不定方程。
下面将证明若一个箱子能被完全塞满,则塞满商品后的箱子的每一个横断面都满足面积的不定方程。
定理1:给定一个箱子(a,b,c),体积为V,和N个物体,面积分别为S1,S2,S3,长宽高为(an,bn,cn),体积为Vn,三个面的面积分别为S'n,S''n,S'''n,填充个数为Xn,n为正整数,且0<n<N,如果这个箱子刚好能被填满,那必定有
1. v1x1+v1x1+v1x1+v1x1+v1x1......+vnxn=V,G=(V1,V2......Vn),G|V,x={x|0<=x<k,x与k均为正整数}
2. 且对于箱子的任一横断面,都有∑S'nXn+S''nYn+S'''nZn=Sp。0< p < 3,0 < n < N,x={x|0<=x<k,x与k均为正整数},y={y|0<=y<k,y与k均为正整数},z={z|0<=z<k,z与k均为正整数}
现在来证明第2点。
使用反证法。
假设箱子能够被这N个物体填满,但对于某一横断面ε,不定方程∑S'nXn+S''nYn+S'''nZn=Sk没有解。设∑S'nXn+S''nYn+S'''nZn=Sk-ΔS=S'k有解。
现设横断面刚好穿过q个物体,即∑Xn+Yn+Zn=q,q个物体中最短的高为Hmin,则箱子中存在着ΔS*Hmin的空隙,这与箱子能被填满的题设矛盾。
现又假设ΔV=∑ΔSr*Hmin=ΔS*Hmin能被N个物体填满(否则就会出现上述结论中的空隙)。
设每个小空隙ΔSr*Hmin都可被N个物体中的某几种组合填满(否则还是会存在空隙)。因此在不对物体进行切割的情况下,填满这几个小空隙,必然有∑S'nX'n+S''nY'n+S'''nZ'n=ΔSr。
由于ΔS=∑ΔSr=∑∑S'nX'n+S''nY'n+S'''nZ'n,所以实际上Sk=S'k+ΔS=∑S'nXn+S''nYn+S'''nZn+∑∑S'nX'n+S''nY'n+S'''nZ'n=∑S'nX''n+S''nY''n+S'''nZ''n有解。这此假设矛盾。
现又设横断面没有穿过任何物体。即q=0。
q=0的情况下,即ΔS=Sk,由箱子可以被物体填满可知,箱子的各个面一定可以被各个物体的某个面的表面积所覆盖。所以关于面积的不定方程∑S'nXn+S''nYn+S'''nZn=Sk也一定有解,这又与假设矛盾。
命题得证。
对于定理1的描述有一个直观的想象, 如果把所有的物体都看成是一个豆腐块,箱子想象成一个更大的豆腐块,则只要沿着豆腐块各边平行的方向一刀切下去,则每一刀要么什么都没切中,要么切出来的表面积等于各个小豆腐块表面积之和。
这个定理有什么用?
首先这定理的逆命题,加上一定的约束条件后,也是成立的。
定理2:给定一个箱子(a,b,c),体积为V,和N个物体,面积分别为S1,S2,S3,长宽高为(an,bn,cn),体积为Vn,三个面的面积分别为S'n,S''n,S'''n,填充个数为Xn,n为正整数,且0<n<N,如果
1. v1x1+v1x1+v1x1+v1x1+v1x1......+vnxn=V,G=(V1,V2......Vn),G|V,x={x|0<=x<k,x与k均为正整数}
2. 不限物体数量,不要求所有物体都能被用上。
3. 如果能找到足够多的不定方程的正整数解,使在填充的过程中,每个最低面Sp关于面积的不定方程∑S'nXn+S''nYn+S'''nZn=Sp总有正整数解,其中0< p < 3,0 < n < N,且∑Xn+ Yn + Zn ≠ 0(即解不全为0,又称之为非平凡解),且由该解得到的物体的最高立高Hmax<=H-ΔH,ΔH为最低面离到底面的距离。
4. 由面积不定方程产生的解一定能满足边不定方程
则箱子一定可以被填满。
证明:
2-1 是定理1-2的逆命题,不作额外阐述。
2-2 使用数学归纳法。设填充的步数为k,当k=1时,箱子没有任何填充物,最低面为箱子的任意一个面。由题设知,在箱子的某一面Sp总能找到∑S'nXn+S''nYn+S'''nZn=Sp的非负整数解。这相当于找到了一个办法,可以在只使用N个物体中的q个来完全覆盖最低面。设这q个中最高的物件最高为Hmax,由题设知,这种填充方案不会超过箱子的空间范围。又设最矮的物体高为Hmin,面积为Smin,则至少有Sp*Hmin的体积是完全填满的。
这里需要对q的值进行分别讨论,如果q=1,则实际上底面是被同一个物体完全填充满。
当Hmin|H,箱子肯定能被完全填充,命题得证。
当Hmin不整除H,由题设知∑S'nXn+S''nYn+S'''nZn=Sp存在着另外一种解,使q!=1或q=1,但填充物已不相同。
如果q>=2,设当前次矮高为H'min,则最低面变成高为H=Hmin的平面。这时,箱子的最低立面为H=Hmin,设最低立面的表面积为Sp'=∑ΔSp',由题设知,针对每一个最低立面,都有关于面积的不定方程的非平凡整数解(解不全为0),且解的最高立高不超过H-Hmin。这时,可以以该解作为第二步填充的方案,假设该方案中最低立高为Hmin',则至少又有Sp'*ΔH的空间被完全填满。其中ΔH=MIN(Hmin',H'min)。
现假设经过k次填充后,已有Vk=Sp * Hk的体积被填满,若箱子还有剩余空间,最低面面积Sl对于方程∑S'nXn+S''nYn+S'''nZn=Sl均存在非平凡解,且满足最高立高与立面高之和不超出箱子的高。那么一定存在Hk+1=Hk+ΔH<=H,使Vk+1 = Sp * Hk+1>Sp *Hk;
根据数学上的夹逼准则,Hk一直增大,ΔH趋向于0,Hk趋向于H,Vk趋向于V。命题得证。
定理2 从数学上给出了一个完整的验证一个箱子能否达到100%填充率的办法,当一个箱子的填充率能达到100%时,通过该办法还能准确地获得详细的填充方案。
2-1-2 实际应用例子
下面通过一个具体例子来说明。
现有一个箱子B长宽高为(5,7,6),商品A(2,2,2) 6个,B(1,4,6) 3个,C(3,1,6) 5个。请问,能不能用这三商品把这个箱子填满。
求解:
第一步先求体积的不定方程,8x + 24y + 18z= 210。把个数代入,刚好满足方程。这证明从体积上是满足的。
第二步利用定理2来求切箱方案。我们知道箱子有三个面,我们先来求底面(5,7)的关于面积的不定方程。因为每个商品都有3个不同的面,所以总共有33=27个不定方程。但由商品A是一个正方体,每个面面积都一样,所以实际不定方程只有9个。现分别把这9个不定方程逐一列出来
4x+4y+3z=35 (1)
4x+6y+3z=35 (2)
4x+24y+3z=35 (3)
4x+4y+6z=35 (4)
4x+6y+6z=35 (5)
4x+24y+6z=35 (6)
4x+4y+18z=35 (7)
4x+6y+18z=35 (8)
4x+24y+18z=35 (9)
根据不定方程的性质,可以快速排除掉4,5,6,7,8,9号方程,剩下1,2,3。
再研究3号方程,发现y只能取0或1。
对于y取1的情况,底面已经被B商品占满,A商品是绝对塞不下的,底面情况如下图所示:
B |
B |
B |
B |
- |
B |
B |
B |
B |
- |
B |
B |
B |
B |
- |
B |
B |
B |
B |
- |
B |
B |
B |
B |
- |
B |
B |
B |
B |
- |
- |
- |
- |
- |
- |
令y = 1,x=0。方程简化成3z=11,显然无整数解。
如果y取0,则方程实为(1),(2)方程的一种y=0时的特殊情况。所以实际上有效的方程仅为1,2。
现在我们来求4x+4y+3z=35,4x+6y+3z=35 这两个丢番图方程。
求多元一次丢番图方程的通常做法是消元法,通过消元法来使不定方程变成二元一次方程。
4X+4Y+3Z = 35 ==> 4R+3Z=35(其中4R = 4X + 4Y) ,通过解这个丢番图方程,可以得通解R=35+3n,Z=-35-4n,由于R>0,Z > 0,得-35 / 3<n < - 35/4 ==> -11 <= n <= -9,代入得非凡解(2,9),(5,5),(8,1)。
这三个非凡解中,(2,9)是不可能是问题的解,因为B的商品只有5个
再将R=5,8回代到不定方程4R=4X+4Y中,又得2个不定方程4X+4Y=20(1-1),4X+4Y=32(1-2),它们的通解均分别为{X,Y|X=0+n,Y=5-n,0<=n<=5},{X,Y|X=0+n,Y=8-n,0<=n <= 8}
又由于X<=6,Y<=3,所以实际上针对(1-1)n只能2<=n<=5,(1-2)只能取5<=n<=6。这样得到问题的一组可行解为:
{2,3,5},{3,2,5},{4,1,5},{5,0,5},{5,3,1},{6,2,1}。
我们把{2,3,5}方案进行代入尝试。我们得到这样一个填充方案:
其中
A |
A |
A |
A |
B |
B |
B |
B |
C | ||||
C | ||||
C | ||||
A |
A |
B |
B |
B |
A |
A |
B |
B |
B |
A |
A |
B |
B |
B |
A |
A |
B |
B |
B |
C |
C |
C |
C |
C |
C |
C |
C |
C |
C |
C |
C |
C |
C |
C |
刚好能填满整个底面。又由于c跟b的立高为6<=6,a的立高为2,这证明{2,3,5}的确是一个能进行有效填充的解。
现在我们来计算一下,箱子还剩下的空间,V=210-2*8-24*3-5*18=48。而且商品B,C都用完了。
现在我们继续进行第二步填充。最低立面是一个2*4体积的矩形区域。
因为B,C都用完了,所以不定方程组退化为
4X=8,易求得X=2。继续填充得
A |
A |
A |
A |
A |
A |
A |
A |
空余面积被完全填满,由于填充物仅为A,所以最小立高ΔH=2。
继续重覆上述步骤,最终可以得到一个装填率为100%的箱子。
在整个算法的执行过程中,我们仅做了3次二元一次不定方程的求解。而使用欧几里德算法(又叫辗转相除法)求解二元一次不定方程的速度是很快的(时间复杂度为O(logN))。所以算法在平均运行速度上不会差于目前所知的算法。更重要的是整个算法在问题肯定有最优解的情况下,能一次求得最优解。
算法到了这里,基本的思路已经出来,但实际上刚才的例子中有一个问题还没有很好解决。就是怎么能让算法自己知道,有一些貌似行得通的解实际上是行不通的呢,例如4x+24y+3z=35,有解(2,1,1)。但上面已经很清楚地分析到,这样的填充方案是不行的。如果由人来判断的话,当然不是问题,但对于机器而言,却需要更准确的操作步骤。
这就是定理2中第4个约束条件的意义所在。
这里进一步引入关于边的不定方程。在刚才的例子中,解暗示我们用2个2*2的面,1个4*6的面,1个1*3的面能填满一个5*7的平面。但在不切割这些面的前提下,能否达到这样的效果呢?
考察下面的不定方程:
2x+y+z=5 (1)
2x+y+z=7 (2)
x={0,2},y,z={1,2,3,4,6}
这是一组关于边的不定方程,含义是在不切割面的前提下,若底面能被物件的各个侧面填充,则沿着长和宽的方向一定可以找出这样组合令各个面的边长满足此不定方程。
使用与前面相似的办法,我们针对(1)来逐步对底面进行填充。
令x=0,y=2,z=3,这组解表示,我们用A的一条边2,C的一条边3,可以填满底边,如下图所示:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A |
A |
|
|
|
A |
A |
C |
C |
C |
这样A和C就各用掉一个,只剩下1个A和1个B了。这时最矮边为3,即问题又变成为求解不定方程2X+4Y=3,2X+6Y=3,显然都没有整数解。求解失败。
再令x=0,y=1,z=4,则底面的情况如下图所示:
|
|
|
|
|
|
B |
B |
B |
B |
|
B |
B |
B |
B |
|
B |
B |
B |
B |
C |
B |
B |
B |
B |
C |
B |
B |
B |
B |
C |
B |
B |
B |
B |
参照上面的做法,用了一个B和C后,得到一个一元一次方程2X=1,显然又是没有整数解的。
使用同样的办法,可以一一验算(1)中所有解的情况,会发现解都不是存在的。
2-1-3 关于边的不定方程
事实上,使用类似证明体积填充率的方法,可以得到一个证明平面填充率的方法,并推导出如下定理。
定理3(面填充定理)若一个矩形平面S=A*B能被若个矩形平面Sn=an*bn完全填充,则沿平面S的边A(或B)作切割线,必然有∑an*Xn+bn*Yn=A(或B)成立。
定理4(面完全填充定理)给定一个平面(A,B),面积为S,和N个面(an,bn),面积为Sn,填充个数为Xn,n为正整数,且0<n<N,如果
1. S1x1+S2x2+S3x3+...+vnxn=S,G=(S1,S2......Sn),G|S,x={x|0<=x<k,x与k均为正整数}
2. 不限填充面数量,不要求所有面都能被用上。
- 如果能找到足够多的不定方程的正整数解,使在填充的过程中,每个最矮边Dp关于边的不定方程∑anXn+bnYn=Dp总有非凡解,且由该解得到的另一边的最高边Hmax<=H-ΔH,ΔH为最矮边离底边的距离。
则平面一定可以被填满。
证明方法跟前面的定理2相似,这里就不再赘述。
2-2 快速装箱算法的具体实现