算法笔记(7):01背包问题与完全背包问题的内存优化
一般实现代码
- 01背包问题
const int max_n = 105;
const int max_m = 10005;
int n, m;//n种物品,总重不超过m
int dp[max_n][max_m];//dp[i][j]指从前i个物品中选,总重不超过j时的背包最大价值
int w[max_n], v[max_n];
void solve()
{
for (int i = 1; i <= n;i++)
for (int j = 1; j <= m;j++)
{
if(w[i]>j)
dp[i][j] = dp[i - 1][j];
else
{
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
}
}
}
- 完全背包问题
const int max_n = 105;
const int max_m = 10005;
int n, m;//n种物品,总重不超过m
int dp[max_n][max_m];//dp[i][j]指从前i种物品中选,总重不超过j时的背包最大价值
int w[max_n], v[max_n];
void solve()
{
for (int i = 1; i <= n;i++)
for (int j = 1; j <= m;j++)
{
if(w[i]>j)
dp[i][j] = dp[i - 1][j];
else
{
dp[i][j] = max(dp[i - 1][j], dp[i][j - w[i]] + v[i]);
}
}
}
内存优化
- 01背包问题
在编写背包问题的代码中,我们注意到dp数组大小为n*m,占用了很多内存,故我们可以重复使用一维数组来减少内存消耗。
直接理解这个过程并不简单,所以最好从二维数组的思路出发,再过渡到一维。
- 首先从动态规划的状态转移方程开始思考
w [ i ] > j , d p [ i ] [ j ] = d p [ i − 1 ] [ j ] w[ i ] > j, dp[ i ][ j ] = dp[ i-1 ][ j ] w[i]>j,dp[i][j]=dp[i−1][j];
w [ i ] < = j , d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) w[ i ] <= j, dp[ i ][ j ] = max(dp[ i - 1 ][ j ], dp[ i - 1 ][ j - w[ i ] ]+v[ i ]) w[i]<=j,dp[i][j]=max(dp[i−1][j],dp[i−1][j−w[i]]+v[i]).
我们注意到,第i层的dp状态完全是由i-1层所决定的,与[1,i-2]没有关系,而递推过程又是不断向后进行的,即[1,i-2]层的递推结果已经没有作用了,由此可以想到,用一个一维数组来存放i-1层的结果,在第i层重复使用这个数组就可以了。
这时就存在一个问题,即我们递推的方向,再次观察状态转移方程,当我们递推
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 时,要用到
d
p
[
i
−
1
]
[
j
−
w
[
i
]
]
dp[i-1][j-w[i]]
dp[i−1][j−w[i]] 的值,这里的
j
−
w
[
i
]
j-w[i]
j−w[i] 是在
j
j
j 的前面的,如果从前向后递推的话,前面的值会被新值覆盖,显然会造成错误结果,故我们应从后往前递推,下面给出代码。
const int max_n = 105;
const int max_m = 10005;
int n, m; //n种物品,总重不超过m
int w[max_n], v[max_n];
int dp[max_m];
void solve()
{
for (int i = 1; i <= n;i++)
for (int j = m; j >= 1;j--)
{
if(w[i]>j)
{
dp[j] = dp[j];
}
else
{
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
}
- 完全背包问题
与上面同理,我们也考虑其状态转移方程
w [ i ] > j , d p [ i ] [ j ] = d p [ i − 1 ] [ j ] w[ i ] > j, dp[ i ][ j ] = dp[ i-1 ][ j ] w[i]>j,dp[i][j]=dp[i−1][j];
w [ i ] < = j , d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − w [ i ] ] + v [ i ] ) w[ i ] <= j, dp[ i ][ j ] = max(dp[ i - 1 ][ j ], dp[ i ][ j - w[ i ] ]+v[ i ]) w[i]<=j,dp[i][j]=max(dp[i−1][j],dp[i][j−w[i]]+v[i]).1
观察状态转移方程可得,第i层的状态与第i层与i-1层有关,并且递推 d p [ i ] [ j ] dp[i][j] dp[i][j] 时需要 d p [ i ] [ j − w [ i ] ] dp[i][j-w[i]] dp[i][j−w[i]] 的值,故我们必须进行正向递推。下面给出代码。
const int max_n = 105;
const int max_m = 10005;
int n, m; //n种物品,总重不超过m
int w[max_n], v[max_n];
int dp[max_m];
void solve()
{
for (int i = 1; i <= n;i++)
for (int j = 1; j <= m;j++)
{
if(w[i]>j)
{
dp[j] = dp[j];
}
else
{
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
}
总结
关于这类问题的思考点应该从二维出发,先从二维考虑其状态转移方程,之后再推导到一维,相对而言便于理解很多。
这里解释一下这个方程, w [ i ] < = j w[i]<=j w[i]<=j时, d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] ) ( k > = 0 & & k ∗ w [ i ] < = j ) dp[i][j]=max(dp[i-1][j-k*w[i]]+k*v[i])(k>=0\&\&k*w[i]<=j) dp[i][j]=max(dp[i−1][j−k∗w[i]]+k∗v[i])(k>=0&&k∗w[i]<=j),我们考虑 d p [ i ] [ j − w [ i ] ] dp[i][j-w[i]] dp[i][j−w[i]],发现两者进行的循环比较大致相同,即 d p [ i ] [ j − w [ i ] ] = m a x ( d p [ i − 1 ] [ j − ( k + 1 ) ∗ w [ i ] ] + k ∗ v [ i ] ) ( k > = 0 & & ( k + 1 ) ∗ w [ i ] < = j ) dp[i][j-w[i]]=max(dp[i-1][j-(k+1)*w[i]]+k*v[i])(k>=0\&\&(k+1)*w[i]<=j) dp[i][j−w[i]]=max(dp[i−1][j−(k+1)∗w[i]]+k∗v[i])(k>=0&&(k+1)∗w[i]<=j),将两个公式合并,我们便可以得到上面的公式。 ↩︎