一、01背包问题
有N件物品和一个最多能装重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
解决方法:声明一个 大小为 m[n][w] 的二维数组,m[ i ][ j ] 表示 在面对第 i 件物品,且背包容量为 j 时所能得到的最大价值 ,那么咱们能够很容易分析得出 m[i][j] 的计算方法,
(1) j < w[i] 的状况,这时候背包容量不足以放下第 i 件物品,只能选择不拿m[ i ][ j ] = m[ i-1 ][ j ]
(2)j>=w[i] 的状况,这时背包容量能够放下第 i 件物品,咱们就要考虑拿这件物品是否能获取更大的价值,
若是拿取,m[ i ][ j ]=m[ i-1 ][ j-w[ i ] ] + v[ i ]。 这里的m[ i-1 ][ j-w[ i ] ]指的就是考虑了i-1件物品,背包容量为j-w[i]时的最大价值,也是至关于为第i件物品腾出了w[i]的空间。
若是不拿,m[ i ][ j ] = m[ i-1 ][ j ] , 同(1)t
到底是拿仍是不拿,比较这两种状况哪种价值最大。
由此能够获得状态转移方程:
if(j>=w[i]) m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]); else m[i][j]=m[i-1][j];
例:在使用动态规划算法求解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]数组。
第一列为物品序号i,第二列为背包容量j
如m[2][6],在面对第二件物品,背包容量为6时咱们能够选择不拿,那么得到价值仅为第一件物品的价值8,若是拿,就要把第一件物品拿出来,放第二件物品,价值10,那咱们固然是选择拿。m[2][6]=m[1][0]+10=0+10=10;依次类推,获得m[6][12]就是考虑全部物品,背包容量为C时的最大价值。
for(int i=1;i<=n;i++){
for(int j=1;j<=bagweight;j++)
{
if(j>=w[i])
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
m[i][j]=m[i-1][j];
}
}
该公式的空间复杂度为O(nw), 可以用一位数组进行优化,
在使用二维数组的时候,递推公式:m[i][j] = max(m[i - 1][j], m[i - 1][j - weight[i]] + value[i]);
第i层的值由第i-1层的值而来,所以公式可改为
m[j] = max(m[j], m[j - weight[i]] + value[i]);
因为m[j]的值要由m[j-weight[i]]推倒出,所以要进行逆序遍历
代码如下:
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
m[j] = max(m[j], m[j - weight[i]] + value[i]);
}
}
二、完全背包问题
有N件物品和一个最多能装重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都可以重复使用,求解将哪些物品装入背包里物品价值总和最大。
这个问题非常类似于 01 背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取 0 件、取 1 件、取 2件……直至取⌊ W / value[i]⌋ 件等许多种。
按照01背包的思路,可得公式f[i,j] = max(f[i-1, j], f[i-1, j-k*value[i]] + k*value[i])
代码如下:
for(int i = 1 ; i<=n ;i++)
for(int j = 0 ; j<=bagweight ;j++)
{
for(int k = 0 ; k*v[i]<=j ; k++)
f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
时间复杂度太高,可对公式进行优化
f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)
f[i , j-v]= max( f[i-1,j-v] , f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....)
由上两式,可得出如下递推关系:
f[i][j]=max(f[i-1][j], f[i,j-v]+w)
核心代码可优化为
for(int i = 1 ; i <=n ;i++)
for(int j = 0 ; j <=m ;j++)
{
if(j-v[i]>=0)
f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]);
}
完全背包的公式与01背包只有下标不同
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);//01背包
f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);//完全背包问题
因此也可以优化为一维数组,因此公式可优化为 f[j] = max(f[j],f[j-v[i]]+w[i]);
核心代码可以写为
for(int i = 1 ; i<=n ;i++)
for(int j = v[i] ; j<=m ;j++)//注意了,这里的j是从小到大枚举,和01背包不一样
{
f[j] = max(f[j],f[j-v[i]]+w[i]);
}
01背包更新f[i][j] 用的是 f[i-1][j - v] 和 f[i-1][j],完全背包更新用的是f[i][j - v] 和 f[i - 1]f[j] 在一维中 f[i-1][j] 就是 f[j], 而01背包的f[i-1][j-v]是i-1层 , 更新的时候第i层下标大的用第i-1层下标小的 ,需要逆序枚举,完全背包的f[i][j-v] 就是用的第i层 ,更新的时候第i层下标大的用第i层下标小的,需要正序枚举。