给定 n n n种物品和一个背包,物品 i i i( 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n)的重量是 ω i \omega_i ωi,其价值是 v i v_i vi,背包的容量为 C C C,对于每种物品只有两种选择,即装入背包或不装入背包。问题是如何选择装入背包的物品使得装入背包的物品的总价值最大?
递推方程
V ( i , j ) = { V ( i − 1 , j ) j < ω i m a x { V ( i − 1 , j ) , V ( i − 1 , j − ω i ) + v i } j ≥ ω i V(i, j) = \begin{cases} V(i-1, j) & j < \omega_i \\ max\{ V(i-1, j), V(i-1, j-\omega_i)+v_i \} & j\geq \omega_i \end{cases} V(i,j)={V(i−1,j)max{V(i−1,j),V(i−1,j−ωi)+vi}j<ωij≥ωi
解释说明:
设 n n n个物品的重量存储在数组w[n]中,价值存储在数组v[n]中,背包的容量为C,数组V[n+1][C+1]存放迭代结果,其中V[i][j]表示前 i i i个物品装入容量为 j j j的背包所能获得的最大价值,数组x[n]存储装入背包的物品。
初始化的条件为将前面的 i i i个物品装入容量为0的背包中(对应着二维数组V中的第一列)和将0个物品装入容量为 j j j的背包(对应着二维数组V中的第一行)
对应的C++代码如下:
/*
* 0/1背包问题
* 3/15/2019
*/
#include
#include
#include
#include
using namespace std;
/*
* w:重量数组
* v:价值数组
* n:物品数
* C:背包容量
*/
int KnapSack(int *w, int *v,int n, int C)
{
// V:迭代计算的结果
int **V = new int*[n+1];
for(int i=0; i<=n; i++)
V[i] = new int[C+1];
//初始化
for(int j=0; j<=C; j++)
{
V[0][j] = 0;
}
for(int i=0; i<=n; i++)
{
V[i][0] = 0;
}
// for(int i=0; i<=n; i++)
// {
// for(int j=0; j<=C; j++)
// cout << V[i][j] << " ";
// cout << endl;
// }
for(int i=1; i<=n; i++) //遍历物品
{
for(int j=1; j<=C; j++) //遍历容量,注意j代表的就是当前背包的容量
{
if(w[i] > j) //装不进对应于第一种情况
{
V[i][j] = V[i-1][j];
}
else //容量决定理论上可以装进,对应于第二种情况
{
V[i][j] = max(V[i-1][j], V[i-1][j-w[i]]+v[i]);
}
}
}
//打印代价矩阵的信息
for(int i=0; i<=n; i++)
{
for(int j=0; j<=C; j++)
cout << setw(3) << V[i][j] << " ";
cout << endl;
}
//检测选中的节点
int *x = new int[n+1]; //表示选中与否
for(int i=n, j = C; i>0; i--)
{
if(V[i][j] > V[i-1][j]) //装入该物品
{
x[i] = 1; //被选中了
j -= w[i]; //更新背包的剩余容量
}
else
{
x[i] = 0; //表示没有被选中
}
}
for(int i=1; i<=n; i++)
{
if(x[i] == 1)
{
cout << "物品" << i << "被选中, " << "重量: " << w[i] << "价值: " << v[i] << endl;
}
}
return V[n][C];
}
int main()
{
fstream is("input.txt");
int n, C;
is >> n >> C;
int *w = new int[n+1];
int *v = new int[n+1];
for(int i=1; i<=n; i++)
{
is >> w[i];
}
for(int i=1; i<=n; i++)
{
is >> v[i];
}
// for(int i=0; i<=n; i++)
// cout << w[i] << " " << v[i] << endl;
// cout << C << endl;
cout << "最大价值: " << KnapSack(w, v, n, C) << endl;
return 0;
}
input.txt
5
10
2 2 6 5 4
6 3 5 4 6
程序运行结果如下:
程序过程推导:
求解第一个阶段(i = 1)的子问题即装入前一个物品确定各种情况下背包能够获得的最大价值
j = 1 < 2 = ω 2 j = 1 < 2 = \omega_2 j=1<2=ω2无法装入,对应于情况1 V[1][1] = V[0][1] = 0
j = 2 = ω 2 j = 2 = \omega_2 j=2=ω2可以装入,对应于情况2,此时max{V[0][2] = 0, V[0][0] + 6} = 6
j = 3 > ω 2 j = 3 > \omega_2 j=3>ω2可以装入,对应于情况2,此时max{V[0][3] = 0, V[0][1] + 6} = 6
同理可得剩余的V[1][j] = 6
求解第二个阶段(i = 2)的子问题即装入前两个物品
容量 j = 1 < 2 = ω 2 j = 1 < 2 = \omega_2 j=1<2=ω2无法装入,对应于情况1此时V[2][1] = V[1][1] = 0
容量 j = 2 = 2 = ω 2 j = 2 = 2 = \omega_2 j=2=2=ω2可以装入,对应于情况2此时max{V[1][2] = 6, V[1][0] + 3 = 3} = 6
容量 j = 3 > 2 = ω 2 j = 3 > 2 = \omega_2 j=3>2=ω2可以装入,对应于情况2此时max{V[1][3] = 6, V[1][1] + 3 = 3} = 6
容量 j = 4 > 2 = ω 2 j = 4 > 2 = \omega_2 j=4>2=ω2可以装入,对应于情况2此时max{V[1][4] = 6, V[1][2] + 3 = 9} = 9
同理可得剩余的V[2][j] = 9
求解第三个阶段(i = 3)的子问题即装入前三个物品
容量 j = 1 < 6 = ω 3 j = 1 < 6 = \omega_3 j=1<6=ω3 无法装入,对应于情况1,此时V[3][1] = V[2][1] = 0
容量 j = 2 < 6 = ω 3 j = 2 < 6 = \omega_3 j=2<6=ω3 无法装入,对应于情况1,此时V[3][2] = V[2][2] = 6
容量 j = 3 < 6 = ω 3 j = 3 < 6 = \omega_3 j=3<6=ω3 无法装入,对应于情况1,此时V[3][3] = V[2][3] = 6
容量 j = 4 < 6 = ω 3 j = 4 < 6 = \omega_3 j=4<6=ω3 无法装入,对应于情况1,此时V[3][4] = V[2][4] = 9
容量 j = 5 < 6 = ω 3 j = 5 < 6 = \omega_3 j=5<6=ω3 无法装入,对应于情况1,此时V[3][5] = V[2][5] = 9
容量 j = 6 = 6 = ω 3 j = 6 = 6 = \omega_3 j=6=6=ω3可以装入,对应于情况2此时max{V[2][6] = 9, V[2][0] + 5 = 5} = 9
容量 j = 7 > 6 = ω 3 j = 7 > 6 = \omega_3 j=7>6=ω3可以装入,对应于情况2此时max{V[2][7] = 9, V[2][1] + 5 = 5} = 9
容量 j = 8 > 6 = ω 3 j = 8 > 6 = \omega_3 j=8>6=ω3可以装入,对应于情况2此时max{V[2][8] = 9, V[2][2] + 5 = 11} = 11
容量$j = 9 > 6 = \omega_3 KaTeX parse error: Expected '}', got '\[' at position 19: …入,对应于情况2此时max{V\̲[̲2][9] = 9, V\[2…j = 10 > 6 = \omega_3$可以装入,对应于情况2此时max{V[2][10] = 9, V[2][4] + 5 = 14} = 14
求解第四个阶段(i = 4)的子问题即装入前四个物品
容量 j = 1 < 5 = ω 4 j = 1 < 5 = \omega_4 j=1<5=ω4无法装入,对应于情况1,此时V[4][1] = V[3][1] = 0
容量 j = 2 < 5 = ω 4 j = 2 < 5 = \omega_4 j=2<5=ω4无法装入,对应于情况1,此时V[4][2] = V[3][2] = 6
容量 j = 3 < 5 = ω 4 j = 3 < 5 = \omega_4 j=3<5=ω4无法装入,对应于情况1,此时V[4][3] = V[3][3] = 6
容量 j = 4 < 5 = ω 4 j = 4 < 5 = \omega_4 j=4<5=ω4可以装入,对应于情况1,此时V[4][4] = V[3][4] = 9
容量 j = 5 = 5 = ω 4 j = 5 = 5 = \omega_4 j=5=5=ω4可以装入,对应于情况2,此时max{V[3][5] = 9, V[3][0] + 4 = 0 + 4} = 9
容量 j = 6 > 5 = ω 4 j = 6 > 5 = \omega_4 j=6>5=ω4可以装入,对应于情况2,此时max{V[3][6] = 9, V[3][1] + 4 = 0 + 4} = 9
容量 j = 7 = 5 = ω 4 j = 7 = 5 = \omega_4 j=7=5=ω4可以装入,对应于情况2,此时max{V[3][7] = 9, V[3][2] + 4 = 6 + 4} = 10
容量 j = 8 = 5 = ω 4 j = 8 = 5 = \omega_4 j=8=5=ω4可以装入,对应于情况2,此时max{V[3][8] = 11, V[3][3] + 4 = 6 + 4} = 11
容量 j = 9 = 5 = ω 4 j = 9 = 5 = \omega_4 j=9=5=ω4可以装入,对应于情况2,此时max{V[3][9] = 11, V[3][4] + 4 = 9 + 4} = 13
容量 j = 10 = 5 = ω 4 j = 10 = 5 = \omega_4 j=10=5=ω4可以装入,对应于情况2,此时max{V[3][10] = 14, V[3][5] + 4 = 9 + 4} = 14
求解第五个阶段(i = 5)的子问题即装入前五个物品
容量 j = 1 < 4 = ω 5 j = 1 < 4 = \omega_5 j=1<4=ω5无法装入,对应于情况1,此时V[5][1] = V[4][1] = 0
容量 j = 2 < 4 = ω 5 j = 2 < 4 = \omega_5 j=2<4=ω5无法装入,对应于情况1,此时V[5][2] = V[4][2] = 6
容量 j = 3 < 4 = ω 5 j = 3 < 4 = \omega_5 j=3<4=ω5无法装入,对应于情况1,此时V[5][3] = V[4][3] = 6
容量 j = 4 = 4 = ω 5 j = 4 = 4 = \omega_5 j=4=4=ω5可以装入,对应于情况2,此时max{V[4][4] = 9, V[4][0] + 6 = 6} = 9
容量 j = 5 > 4 = ω 5 j = 5 > 4 = \omega_5 j=5>4=ω5可以装入,对应于情况2,此时max{V[4][5] = 9, V[4][1] + 6 = 6} = 9
容量 j = 6 > 4 = ω 5 j = 6 > 4 = \omega_5 j=6>4=ω5可以装入,对应于情况2,此时max{V[4][6] = 9, V[4][2] + 6 = 12} = 12
容量 j = 7 > 4 = ω 5 j = 7 > 4 = \omega_5 j=7>4=ω5可以装入,对应于情况2,此时max{V[4][7] = 10, V[4][3] + 6 = 12} = 12
容量 j = 8 > 4 = ω 5 j = 8 > 4 = \omega_5 j=8>4=ω5可以装入,对应于情况2,此时max{V[4][8] = 11, V[4][4] + 6 = 15} = 15
容量 j = 9 > 4 = ω 5 j = 9 > 4 = \omega_5 j=9>4=ω5可以装入,对应于情况2,此时max{V[4][9] = 13, V[4][5] + 6 = 15} = 15
容量 j = 10 > 4 = ω 5 j = 10 > 4 = \omega_5 j=10>4=ω5可以装入,对应于情况2,此时max{V[4][10] = 14, V[4][6] + 6 = 15} = 15
结果与最后得出的一致。
一定要注意数组V的大小,需要开[n+1][C+1],因为物品是由一个零行和n个物品组成,容量是由一个零行和C容量组成。
一定要注意下标对应,是从0开始还是从1开始。
在算法 K n a p S a c k KnapSack KnapSack中,第一个 f o r for for循环的时间性能是 O ( C ) O(C) O(C),第二个 f o r for for循环的时间性能是 O ( n ) O(n) O(n),第三个循环是两层嵌套的 f o r for for循环,其时间性能是 O ( n × C ) O(n \times C) O(n×C),第四个 f o r for for循环的时间性能是 O ( n ) O(n) O(n),所以,算法的时间复杂性为 O ( n × C ) O(n \times C) O(n×C)
Online 0/1 Knapsack problem solver