赞
踩
目录
给定种物品(每种物品均只有一个)和一背包。物品i的重量是,其价值为,背包的最大容量为。怎样选择装入背包中的物品,使得其总价值最大?
例如:现有4种物品,其对应的重量和价值如图所示,另有一最大容量为5的背包,求该背包所能装下物品的最大价值?
物品 | 重量 | 价值 |
0 | 2 | 4 |
1 | 1 | 3 |
2 | 4 | 6 |
3 | 3 | 5 |
- dp[i][j]:任取第0~i件物品,放入容量为j的背包,能得到的最大价值
- 例:dp[1][2]=3的含义:
- 任取物品0~物品1,放入容量为2的背包,能得到的最大价值(取物品1放入背包,其价值最大,为3)
dp[i][j] 的定义十分重要,状态方程的书写、数组初始化、遍历顺序和输出结果都必须严格按照该定义进行书写
对于物品,它只有放与不放两种状态,当它能放入背包时,状态转移方程只需取两种状态的最大值; 否则,取不放入时的最大价值。
- if (j < w[i])
- dp[i][j] = dp[i - 1][j];
- else
- dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
从状态转移方程可知,欲求dp[i][j],需先确定dp[i-1][j]和dp[i-1][j-w[i]]的值(即需保证dp[i][j]的左上角数据已经处理完成),故我们需对第0行和第0列进行初始化.
- //初始化第0列(即背包容量为0)
- for (i = 0; i < n; i++)
- dp[i][0] = 0;
- //初始化第0行(即只有物品0)
- for (j = 1; j <= max_weight; j++) {
- if (w[0] <= j)
- dp[0][j] = v[0];
- else
- dp[0][j] = 0;
- }
遍历顺序:求解dp[i][j]只需提前知道其左上角的数据,故以下两种遍历方式均可。
1、先遍历背包再遍历物品(即物品数固定,背包容量逐增至最大,物品再加1)
2、先遍历物品再遍历背包(即背包容量固定,物品数逐增至最大,背包容量再加1)
输出:dp[n-1][max_weight]: n件物品任取,放入容量为max_weight的背包,所得的最大价值。
- //先遍历物品再遍历背包
- for (i = 1; i < n; i++) { //遍历物品
- for (j = 1; j <= max_weight; j++) { //遍历背包
- if (j < w[i])
- dp[i][j] = dp[i - 1][j];
- else
- dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
- }
- }
- //先遍历背包再遍历物品
- for (j = 1; j <= max_weight; j++) { //遍历背包
- for (i = 1; i < n; i++) { //遍历物品
- if (j < w[i])
- dp[i][j] = dp[i - 1][j];
- else
- dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
- }
- }
由状态转移方程(dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]))可知,当dp[i][j]
- //回溯法求解装入物品
- i--; j--; //循环结束后,i=n,j=max_weight+1,故使用dp[n - 1][max_weight]须自减1
- cout << "最大价值时的背包物品为:" << endl;
- while (dp[i][j]&&i>=0) {
- if (dp[i][j] != dp[i - 1][j]) {
- cout << "物品" << i << endl;
- j -= w[i]; //装入物品i后背包的最大容量
- }
- i--; //物品i已经处理完成,接下来讨论物品i-1
- }
1、 使用dp[i][j](最大价值)前一定要对i,j进行处理,否则,运行时会抛出异常;
2、结束条件一定要加上i>=0,否则,运行结果可能会出现物品-1等错误。
3、该法得到的结果只为其中的一组最优解,可能还存在其他最优解,例如:当我们装入物品0(2,4)和物品3(3,5)时,其最大价值也为9.
1、0-1背包问题是背包问题的基础。深入了解0-1背包问题(每件物品只有一个)是掌握完全背包问题(每件物品有无数个)、多重背包问题(不同物品数量不同)和分组背包(物品分为多组,每组最多选一个)的关键;
2、使用dp[i][j]数组时,一定要注意数组的界限问题,避免越界访问造成错误。比如:刚开始的时候,我认为背包不存在容量为0的情况,故没有将第一列初始化为0,导致输出出错,将dp[i][j]全部打印出来后,才知道是dp[1][1]出错了,因为dp[1][1]=max(dp[0][1],dp[0][0]+v[0]),需要用到dp[0][0],造成越界。所以,检验算法的正确性,可以先人工求解出dp[i][j]数组,再用程序将其打印出来,这样有利于我们快速找到问题所在。
转换表(打印)
转换表(人工) | ||||||
0 | 1 | 2 | 3 | 4 | 5 | |
0 | 0 | 0 | 4 | 4 | 4 | 4 |
1 | 0 | 3 | 4 | 7 | 7 | 7 |
2 | 0 | 3 | 4 | 7 | 7 | 9 |
3 | 0 | 3 | 4 | 7 | 8 | 9 |
3、背包问题一定要先明确辅助数组的具体含义再确定转换方程,根据转换方程来确定初始化、遍历顺序和输出结果。
- #include<iostream>
- #include<algorithm>
- using namespace std;
-
- const int max_weight = 5;
- const int n = 4;
-
- int main() {
- int i, j;
- int w[n] = { 2,1,4,3 }; //重量
- int v[n] = { 4,3,6,5 }; //价值
- int dp[n][max_weight+1];//辅助数组
-
- //初始化第0列(即背包容量为0)
- for (i = 0; i < n; i++)
- dp[i][0] = 0;
- //初始化第0行(即只有物品0)
- for (j = 1; j <= max_weight; j++) {
- if (w[0] <= j)
- dp[0][j] = v[0];
- else
- dp[0][j] = 0;
- }
-
- //状态转移
- //先遍历物品再遍历背包
- for (i = 1; i < n; i++) { //遍历物品
- for (j = 1; j <= max_weight; j++) { //遍历背包
- if (j < w[i])
- dp[i][j] = dp[i - 1][j];
- else
- dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
- }
- }
-
- //输出
- cout << dp[n - 1][max_weight] << endl;
-
- //回溯法求解装入物品
- i--; j--; //循环结束后,i=n,j=max_weight+1,故使用dp[n - 1][max_weight]须自减1
- cout << "最大价值时的背包物品为:" << endl;
- while (dp[i][j]&&i>=0) {
- if (dp[i][j] != dp[i - 1][j]) {
- cout << "物品" << i << endl;
- j -= w[i]; //装入物品i后背包的最大容量
- }
- i--; //物品i已经处理完成,接下来讨论物品i-1
- }
-
- return 0;
- }
运行结果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。