本文参考Google OR-Tools官网文档介绍OR-Tools的使用方法。
装箱问题的描述是要将一组给定尺寸的物品放置到具有固定容量的容器中,一般情况下由于容器有容量限制,不可能放入所有物品,那么装箱问题的目标是要找到限定约束下使得总价值最大或总花费最小的装箱方法。根据我们具体的目标情况,装箱问题又可分为两类:
- 背包问题(Knapsack problem),容器数量是固定的,每个容器有自己的最大容量,而需要分配的物件有各自的价值,我们的目标是让装入容器的物体的总价值最大,并不要求所有物体都装入;
- Bin-packing问题,容器容量是相同的,但是数量不固定,我们的目标是用最少的容器来存放所有的物件。
OR-Tools针对典型的单背包问题提供了专用的接口,对于更一般的多背包问题和Bin-packing问题则需要使用通用的整数规划接口来计算。这篇文章里我就参考官方教程演示用OR-Tools对单背包问题、多背包问题和Bin-Packing问题进行建模。
1 单背包问题
如果背包问题中只有一个背包,那么就是单背包问题,一般也成为0-1背包问题,因为它可以用下面的方程式来描述问题:
用0-1变量 x i x_i xi表示第 i i i个物件是否放入背包, v i v_i vi表示物件 i i i的价值, w i w_i wi则表示物件的重量, W W W则是背包的最大承重,那么整个问题可表示为:
m a x i m i z e ∑ i = 1 n v i x i s u b j e c t t o ∑ i = 1 n w i x i ≤ W x i ∈ { 0 , 1 } \begin{aligned} maximize\quad &\sum_{i=1}^n v_ix_i\\ subject\ to\quad& \sum_{i=1}^nw_ix_i\leq W \\ & x_i \in \{0,1\} \end{aligned} maximizesubject toi=1∑nvixii=1∑nwixi≤Wxi∈{
0,1}
当然实际生活中可能限制背包存放物体数量的不仅是重量,还有体积等属性,因此上面的定义更严格的来说应该是单维单背包问题,如果有多维限定,那么在每一维存放物体的总量都不能超过背包的限定值。
我们新建一个.NET Core控制台应用,自定义一个单背包问题的数据:
//values[i], the value of item i
long[] values = {
360, 83, 59, 130, 431, 67, 230, 52, 93,
125, 670, 892, 600, 38, 48, 147, 78, 256,
63, 17, 120, 164, 432, 35, 92, 110, 22,
42, 50, 323, 514, 28, 87, 73, 78, 15,
26, 78, 210, 36, 85, 189, 274, 43, 33,
10, 19, 389, 276, 312 };
//weights[i,j], the weight of weights[i][j]
long[,] weights = {
{
7, 0, 30, 22, 80, 94, 11, 81, 70,
64, 59, 18, 0, 36, 3, 8, 15, 42,
9, 0, 42, 47, 52, 32, 26, 48, 55,
6, 29, 84, 2, 4, 18, 56, 7, 29,
93, 44, 71, 3, 86, 66, 31, 65, 0,
79, 20, 65, 52, 13 } };
long[] capacities = {
850 };
OR-Tools提供了一个KnapsackSolver来专门处理单背包问题,我们定义这个KnapsackSolver对象,并且指定使用分支界定算法
KnapsackSolver solver = new KnapsackSolver(
KnapsackSolver.SolverType.KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER,
"KnapsackExample");
然后就可以初始化KnapsackSolver对象并求解了
solver.Init(values, weights, capacities);
long computedValue = solver.Solve();
Solve()方法的返回值就是算法的最终目标值,而如果要知道那些物件被放置,则需要调用BestSolutionContains()方法查看。
Console.WriteLine("Optimal Value = " + computedValue);
string selectItems = $"Selected item indexs : ";
for (int i = 0; i < values.Length; i++)
{
if (solver.BestSolutionContains(i))
{
selectItems += $"{i}({values[i]}), ";
}
}
Console.WriteLine(selectItems);
完整的程序
using System;
using Google.OrTools.Algorithms;
namespace SingleKnapsackProblem
{
class Program
{
static void Main(string[] args)
{
//Create a knapsack solver, use Branch And Bound algorithm
KnapsackSolver solver = new KnapsackSolver(
KnapsackSolver.SolverType.KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER,
"KnapsackExample");
//values[i], the value of item i
long[] values = {
360, 83, 59, 130, 431, 67, 230, 52, 93,
125, 670, 892, 600, 38, 48, 147, 78, 256,
63, 17, 120, 164, 432, 35, 92, 110, 22,
42, 50, 323, 514, 28, 87, 73, 78, 15,
26, 78, 210, 36, 85, 189, 274, 43, 33,
10, 19, 389, 276, 312 };
//weights[i,j], the weight of weights[i][j]
long[,] weights = {
{
7, 0, 30, 22, 80, 94, 11, 81, 70,
64, 59, 18, 0, 36, 3, 8, 15, 42,
9, 0, 42, 47, 52, 32, 26, 48, 55,
6, 29, 84, 2, 4, 18, 56, 7, 29,
93, 44, 71, 3, 86, 66, 31, 65, 0,
79, 20, 65, 52, 13 } };
long[] capacities = {
850 };
solver.Init(values, weights, capacities);
long computedValue = solver.Solve();
Console.WriteLine("Optimal Value = " + computedValue);
string selectItems = $"Selected item indexs : ";
for (int i = 0; i < values.Length; i++)
{
if (solver.BestSolutionContains(i))
{
selectItems += $"{i}({values[i]}), ";
}
}
Console.WriteLine(selectItems);
}
}
}
2 多背包问题
把单个背包扩展到多个背包,就是多背包问题,对于多背包问题,我们可以采用下面的定义方式:
给出 n n n个物件和 m m m个背包( m ≤ n m\leq n m≤n),并且按如下定义(仍然只是单维):
v j : 物 件 j 的 价 值 w j : 物 件 j 的 重 量 c i : 第 i 个 背 包 的 容 量 \begin{aligned} &v_j : 物件j的价值\\ &w_j : 物件j的重量\\ &c_i : 第i个背包的容量\\ \end{aligned} vj:物件j的价值wj:物件j的重量ci:第i个背包的容量
要让每个背包不超过容量限制的情况下最大化放置的物件的价值总量,即:
m a x i m i z e ∑ i = 1 m ∑ j = 1 n v j x i j s u