异想时刻
最近公司经常要往外发货,每次都要花一些时间计算如何更优地装箱,有时也会出现拆箱重装的现象,很是令人头疼。基于此,我便突发奇想设计一个算法来给出一些装箱参考方案。
物流发货时,我们需要同时考虑重量和体积,在箱子可承受的范围内尽量将箱子装满。鉴于此类应用场景,我们首先想到的可能就是背包问题,而且是多重背包。
假设:
为物品种类,为箱子承重,为箱子体积
对于每种物品,
数量, 重量, 体积,价值,其中,
如果依据多重背包求解的话,需要给出每件物品的价值。在实际物流发货操作中,要考虑运费成本,所以就要尽可能的将箱子装满而且箱数越少越好。这里我只单纯地优先考虑重量轻且体积小的物品装箱(如果有更好的方式欢迎讨论),那么价值。考虑到两个维度导致普通背包解法的时间复杂度会很大,单纯为了最优化装箱问题,这个解法在实际中带来的效益可能并不好,所以有必要进行优化降低时间复杂度。
单调队列
对于多重背包问题,除了基础动态规划解法(时间复杂度)外,还有两种优化方式: 二进制优化(时间复杂度)和单调队列优化(时间复杂度)。这里就不叙述二进制优化了,感兴趣可以了解下,是一种很巧妙的方法。而单调队列优化,顾名思义就是借助了特殊数据结构单调队列来优化多重背包问题。
单条件背包
众所周知,0-1背包的动态规划算式为,那么多重背包就是在其基础上考虑物品数量,算式如下:
如果从背包容量的角度考虑(是不是就成了完全背包问题?!),我们可以把上述公式转换为
将等式左右项的容量都加上,可得到
如果为了方便计算,还可将其变换为
显然,
进而可得,,
可以看到,第项的解其实与前项有关。比如,时,即
0, , , …, , …,
在此序列上各项的最优解只与其前面所有项中的最优解有关。所以我们只需要保存前项的最优解,就可以快速计算出第项的最优解,而无需逐项比较。那么此处就可以借助单调递减队列来保存前项的最优解,其实就是队首。
假设我们在队列中保存的是每项的值,那么队首容量为,其与第项的容量之差。因此,我们可以得到如下两条结论:
若, 则第项还可以容纳个物品i。
若, 则第项还可以容纳所有物品i,即。
这里我们来思考一个问题:在计算前项的最优解时,是否需要遍历比较所有项呢?
毋庸置疑,为可容纳的物品i的个数。其范围为,即,最少容纳0个物品i,最多容纳个物品i。所以,我们只需要考虑前项即可。
至此,我们就得出了更新单调递减队列的两个条件了。
当第项的解比队尾大时,队尾元素出队,直到队列中不存在比其小的项便将其入队, 保证了队列的单调性。
当时,队首元素出队,入队。
在更新第项的解时,只需与最优解队首元素比较即可。
前面的分析是从重量的角度考虑,单从体积的维度考虑也是一样,这里不再赘叙。
多条件背包
那么,多条件的话该如何更新队列和最优解呢?
其实也并不复杂,只需同时考虑重量和体积两个维度即可。例如,当时,
|
|
|
… |
|
… |
|
|
|
|
… |
|
… |
|
|
|
|
… |
|
… |
|
… |
… |
… |
… |
… |
… |
… |
|
|
|
… |
|
… |
|
… |
… |
… |
… |
… |
… |
… |
|
|
|
… |
|
… |
|
由于要同时满足背包的承重和体积,所以队列中保存的值。每项的最优解为
结论
虽然优化后算法的时间复杂度降了一个梯度,但计算时间仍然很长,毕竟实际箱子的承重()和体积()都很大。
后来转念一想,如果是为了将箱子装满,也没有必要采用如此复杂的算法。可以先考虑体积,再考虑重量。另外,为了尽可能同时达到箱子的承重和体积,那就可以优先考虑重量体积比更接近箱子的重量体积比的物品。由于重量体积比很似斜率,姑且就称其为“斜率法”吧。