1、01背包问题
2、01背包问题的优化
3、01背包问题不同条件下的初始化
问题描述:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。
问题转换:已知有一个最多可以装质量W的背包,还有N件物品,第i件物品的质量是w[ i ],价值是v[ i ],每种物品尽可以放一件,可以选者放或者不放,而且不可以超过背包的最大承受的重量
顾名思义,01背包,就是在每一件物品中抉择,是放还是不放,那么咱们先来设一个数组dp[ i ][ j ](意思是:前 i 件物品放到一个容量为 j 的背包中可以获得的最大价值,这前i个物品有的可能放了,有的没有放),
举个栗子:
W = 5 N = 4
w[ 1 ] = 2 w[ 2 ] = 1 w[ 3 ] = 3 w[ 4 ] = 2
v [ 1 ] = 3 v [ 2 ] = 2 v [ 3 ] = 4 v [ 1 ] = 2
那么咱们来看 如果说 第一件物品不放的话:dp[ 1 ][ 5 ] == 0 如果放入 : dp[ 1 ][ 5 - 2 ] == dp[ 1 ][ 3 ] == 3
假设说咱们第一件物品拿了,那么第二件物品如果不拿,相应的背包的重量也不会减少,就有:dp[ 2 ][ 3 ] == d[ 1 ][ 3 ] == 3
假设说咱们第一件物品拿了,那么第二件物品如果 拿,相应的背包的重量 会减少,就有:dp[ 2 ][ 3 - 1] == d[ 1 ][ 3 - 1 ] + v[ 2 ] == 5
所以可以建立递推关系式:
① :dp[ i ][ j ] = dp[ i - 1 ][ j ] 第 i 件物品没有拿,所以背包可以承受的重量依然为 j ,而且价值也不会增多
② :dp[ i ][ j ] = dp[ i - 1][ j - w[ i ] ] + v[ i ] 表示第 i 件物品拿了,所以背包可以承受的重量减去第 i 件物品的重量 ,而且价值增多 v[ i ]
由此可得递推关系为:dp[ i ][ j ] = max( dp[ i - 1 ][ j ] , dp[ i - 1][ j - w[ i ] ] + v[ i ] ) 保证 j >= w[ i ]
代码如下:
#include
#include
#include
#include
using namespace std;
int main()
{
//dp[ i ][ j ](意思是:前 i 件物品放到一个容量为V的背包中可以获得的最大价值,
//这前i个物品有的可能放了,有的没有放),
int W, N, w[100], v[100], dp[100][100];
scanf("%d %d", &W, &N);
for(int i = 1; i <= N; i++) scanf("%d", &w[i]);
for(int i = 1; i <= N; i++) scanf("%d", &v[i]);
memset(dp, 0, sizeof(dp)); // 初始化为0
/* *********************************************** */
for(int i = 1; i <= N; i++){
for(int j = 1; j <= W; 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]); //在可以装得前提下, 看看装还是不装哪一种情况可以使得价值更大;
}
}
/* ********************************************** */
printf("%d\n", dp[N][W]);
return 0;
}
/*
输入:
5 4
2 3
1 2
3 4
2 2
输出:
7
*/
建议大家自己画一个N*W得表格,自己按照程序走一遍,一定会理解得很深刻。
给大家一个栗子,自己比对走一遍也会有不错的效果:
例:0-1背包问题。在使用动态规划算法求解0-1背包问题时,使用二维数组m[i][j]存储背包剩余容量为j,可选物品为i、i+1、……、n时0-1背包问题的最优值。绘制
价值数组v = {8, 10, 6, 3, 7, 2},
重量数组w = {4, 6, 2, 2, 5, 1},
背包容量C = 12时对应的m[i][j]数组。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
1 | 0 | 0 | 0 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 |
2 | 0 | 0 | 0 | 8 | 8 | 10 | 10 | 10 | 10 | 18 | 18 | 18 |
3 | 0 | 6 | 6 | 8 | 8 | 14 | 14 | 16 | 16 | 18 | 18 | 24 |
4 | 0 | 6 | 6 | 9 | 9 | 14 | 14 | 17 | 17 | 19 | 19 | 24 |
5 | 0 | 6 | 6 | 9 | 9 | 14 | 14 | 17 | 17 | 19 | 21 | 24 |
6 | 2 | 6 | 8 | 9 | 11 | 14 | 16 | 17 | 19 | 19 | 21 | 24 |
细心的一定发现刚才用的dp数组得空间是一个N*W的,如果N和W都比较大的情况下,有的题目的内存空间不给你很多,如果继续开辟这么大的数组会报内存超限的错误
下面将会说到怎么把N*W的dp 数组压缩成W大小的空间:
我们可以看到,要想得到 dp[i][j],我们需要知道 dp[i - 1][j] 和 dp[i - 1][j - w[i]],由于我们使用二维数组保存中间状态,所以可以直接取出这两个状态。
大家有没有发现第 i 个物品的是由第 i - 1个物品推出来的,也就是刚才让大家比对的那个表格,你要仔细的走了一遍那个表格,一定会发现,第i行的填充是由第i - 1行推出来的,如果建立一个数组dp[ j ] 那么如果当 i = 2,dp[j]数组存储的是当i=1时候&&背包还可承受重量为w时候的价值,那么咱们就可以直接利用dp[j],作为i - 1时候的价值,但要注意一点,在遍历重量的之后,为了保证当改变dp[j]时,不会影响后面对dp[j]的计算,咱们枚举重量的时候是从大到小枚举的。所以递推方程改为:
1、dp[j] = dp[j] // 不加入第i个物品
2、dp[j] = dp[j - w[i]] + v[i] // 加第i个物品
所以:dp[j] = max( dp[j] , dp[j - w[i]] + v[i] )
如果还不理解,一定要!!!自己走一遍代码,其实自己走一遍什么都懂了!!! 自己举个栗子,一点点的走,或者按我刚才列的那个表格按照代码走一遍。
代码:
#include
#include
#include
#include
using namespace std;
int main()
{
int W, N, w[100], v[100], dp[100];
scanf("%d %d", &W, &N);
for(int i = 1; i <= N; i++) scanf("%d", &w[i]);
for(int i = 1; i <= N; i++) scanf("%d", &v[i]);
memset(dp, 0, sizeof(dp)); // 初始化为0
/* *********************************************** */
for(int i = 1; i <= N; i++){
for(int j = W; j >= w[i]; j--){ //j >= w[i] 保证可以装的下
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);//在可以装得前提下, 看看装还是不装哪一种情况可以使得价值更大;
}
}
/* ********************************************** */
printf("%d\n", dp[W]);
return 0;
}
/*
输入:
5 4
2 3
1 2
3 4
2 2
输出:
7
*/
求最优解的背包问题时,有两种问法:
1、在恰好装满背包的情况下,最多能获得多少价值
2、在不超过背包容量的情况下,最多能获得多少价值
主要的区别为是否要求恰好装满背包。但这两种问法的实现方法是在初始化的时候有所不同。
恰好装满背包的情况:使用二维数组dp[i][j]存储中间状态,其中第一维表示物品,第二维表示背包容量
初始化时,除了dp[i][0] = 0(第一列)外,其他全为负无穷,这样保证了只有装满的时候才会计算价值。
我们虽然是求恰好装满,还是需要枚举所有可以装入背包的物品,只要能装入,还需装入,收益有增加。只不过,由于恰好装满的物品的序列肯定是从第一列某行开始的,且之后的收益肯定是正值。对于非恰好装满的物品序列,其实位置肯定是从第一行某位置开始的,由于此时被初始化为负无穷,在和那些恰好装满物品序列带来的价值时,肯定是小的。所以,我们最后能获得最大值。
#include
#include
#include
#include
using namespace std;
int main()
{
int W, N, w[100], v[100], dp[100][100];
scanf("%d %d", &W, &N);
for(int i = 1; i <= N; i++) scanf("%d", &w[i]);
for(int i = 1; i <= N; i++) scanf("%d", &v[i]);
for(int i = 0; i <= N; i++){ //初始化
for(int j = 1; j <= W; j++){
dp[i][j] = -9999999;
}
dp[i][0] = 0;
}
/* *********************************************** */
for(int i = 1; i <= N; i++){
for(int j = 1; j <= W; 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]); //在可以装得前提下, 看看装还是不装哪一种情况可以使得价值更大;
}
}
/* ********************************************** */
printf("%d\n", dp[N][W]);
return 0;
}
当用一维数组存储时:
使用一维数组dp[j]存储中间状态,维表示背包容量初始化时,除了dp[0] = 0,其他全为负无穷。原因:只有容量为0 的背包可以什么物品都不装就能装满,此时价值为0。其它容量的背包均没有合法的解,属于未定义的状态,应该被赋值为负无穷了
全部初始化为0即可。