动态规划算法典型应用之背包问题
-
问题描述:一个旅行者有一个背包,背包的大小为b,可以放入背包的物品有n种,物品j的重量和价值分别为 w j w_j wj和 v j v_j vj 请问怎么选择放入背包的物品以使得背包的价值最大?
-
有了之前投资问题的基础,我们很容易发现,背包问题也可以用动态规划算法来解决。动态规划算法的前期准备步骤主要是如何确定子问题的规模,用什么参数来确定,以及用几个参数。和投资问题的两个参数(投资前k个,投x万元)相类似,背包问题也要用两个参数,分别是选择前k个物品,背包的空间为y 这两个参数决定。我们用优化函数 F k ( y ) F_k(y) Fk(y)来表示只允许装前k种物品,背包总数不超过y时背包的最大价值。我们计算这个规模的问题时,分两种情况考虑,如果不选择第k个物品,只选择前k-1种物品,而背包仍然是y,也就是 F k − 1 ( y ) F_{k-1}(y) Fk−1(y) ;还有一种情况就是选择了第k个物品,那么剩下的背包空间就是y-w[k],而因为每个物品可以选择多个,所以剩下的背包空间仍然可以在前k个物品中进行选择,那么取得的最大值就是 F k ( y − v [ k ] ) F_k(y-v[k]) Fk(y−v[k])
-
所以最后的递推方程就是 F k ( y ) = m a x { F k − 1 ( y ) , F k ( y − v k ) + w k } F_k(y) = max\{F_{k-1}(y),F_k(y-v_k)+w_k\} Fk(y)=max{Fk−1(y),Fk(y−vk)+wk}
-
我们可以看到背包问题的递推方程在max里面的两个值都运用到了之前规模比较小的子问题,第一个 F k − 1 ( y ) F_{k-1}(y) Fk−1(y) 运用到了 物品种类(k - 1)这个规模参数,而第二个 F k ( y − v k ) + w k F_k(y-v_k)+w_k Fk(y−vk)+wk则在相同物品种类规模k下,运用了背包大小( y − v k y-v_k y−vk)这个规模参数,可以说将规模小的子问题运用得淋漓尽致。而投资问题的递推方程 F k ( x ) = m a x 0 ⩽ x k ⩽ x { f k ( x k ) + F k − 1 ( x − x k ) } F_k(x) = \underset {0\leqslant x_k \leqslant x} {max} \{f_k(x_k) + F_{k-1}(x-x_k)\} Fk(x)=0⩽xk⩽xmax{fk(xk)+Fk−1(x−xk)} 中因为max中第一个值 f k ( x k ) f_k(x_k) fk(xk)收益函数是已知条件,虽然第二个值 F k − 1 ( x − x k ) F_{k-1}(x-x_k) Fk−1(x−xk)同时运用了项目种类(k-1)这个规模参数和投资钱数 x − x k x-x_k x−xk这个规模参数,但是昨天我在写投资问题博客的时候,感知没有那么强,只注意到了 F k − 1 F_{k-1} Fk−1这个项目种类的规模参数,让人忽略了投资钱数这个规模参数。
-
以下是我根据书上的数据,写的程序
//背包问题 旅行者有一个背包,可以放入背包的物品有n种,物品j的重量和价值分别是w[j]和v[j].如果背包的最大重量限制是b,怎么样选择放入背包的物品使得背包的价值最大? //输入 每个物品的体积和价值 和背包限制b //输出 最大价值和具体每个物品如何选取 #include <iostream> #include <algorithm> using namespace std; int main() { int w[5] = {0, 2, 3, 4, 7}; //每个物品的重量w[0]弃用 int v[5] = {0, 1, 3, 5, 9}; //每个物品的价值v[0]弃用 int b = 10; //背包限制 int F[5][11] = {0}; //F[k][y]表示在选择前k个物品,并且背包限重y时,取得的最大收益 int tag[5][11] = {0}; //表示计算优化函数F[k][y]所用到的物品的最大标号 for (int i = 1; i <= 10; i++) //当只取第一种物品的时候,用当前钱购买尽可能多的数量,便能取得最大收益 { F[1][i] = i / w[1] * v[1]; if (i / w[1] > 0) tag[1][i] = 1; //表示所选择物品的最大标号 else tag[1][i] = 0; } for (int i = 2; i <= 4; i++) //枚举选择前2个物品..前3个..前4个 { for (int j = 1; j <= 10; j++) //枚举背包大小 从1到10 { int tmp; if (j - w[i] < 0) //在递推过程种j-w[i]可能会产生负值,这意味着背包刺客能够承受的重量已经小于第k种物品的重量,实际上时不能装的,通过令F[i][j - w[i]]为一个很小的值,从而使 这个值在另一个优化函数比较时被淘汰,从而使得这种装法被排除 tmp = -114514; else tmp = F[i][j - w[i]]; if (F[i - 1][j] > (tmp + v[i])) //比较选择第i个物品或者不选择第i个物品哪个价值更大 { F[i][j] = F[i - 1][j]; tag[i][j] = tag[i - 1][j]; //标明取得最大收益时的物品没有用到i,所以和tag[i - 1][j]的标号相同 } else { F[i][j] = (F[i][j - w[i]] + v[i]); tag[i][j] = i; //最大物品编号为i } } } cout << "最大收益:" << F[4][10] << endl; int x[5] = {0}; //用来统计每个物品被选择的数量,初始都为零 int space = 10; //一共有十个空间 while (1) { int i = tag[4][space]; x[i]++; space -= w[i]; if (space == 0 || i == 0) break; } for (int i = 1; i <= 4; i++) cout << "第" << i << "个物品的数量:" << x[i] << endl; return 0; }
-
运行结果
-
我希望编程不是为了完成老师布置的题目,而是自己发自真地想要学习并掌握某个算法。