背包问题
背包问题是动态规划部分的代表部分,主要有 0-1 背包、完全背包等。
- 0-1 背包
0-1背包中每种物品只有一件,故每种物品要么装进背包,要么没有装进背包,故曰 “0-1”。
问题描述:n 种物品,每种物品只有一件,每种物品的重量为 wieght[i],每种物品的价值为 value[i] ,背包的总容量为 v ,求背包中能装入物品的最大价值总和。
动态规划:dp[i][j] 表示背包容量剩下 j 时考虑装或者不装第 i 件物品的最大价值。
状态转移方程:若 j < weight[i],此时背包容量无法装下物品 i ,则最大价值等于背包容量为 j 时考虑装或者不装物品 i-1 时的最大价值,即 dp[i][j]=dp[i-1][j];若 j>=weight[i],此时背包可以装下物品 i ,此时有两种选择,装下物品 i (但可能装不下 0到i-1之间的物品),或者不装物品 i ,即 dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])。程序如下所示:
void bag01()
{
const int n; //物品种类数
const int w; //背包最大容量
int * weight[n+1]; //各物品重量
int * value[n+1]; //各物品价值
int dp[n+1][v+1]; //背包容量为v时选择装或者不装第n件物品的最大价值
for (int i = 0; i < n+1; i++)
{
dp[i][0]=0;
}
for (int i = 0; i < v+1; i++)
{
dp[0][i]=0;
}
for (int i = 1; i < n+1; i++)
{
for (int j = 1; j < v+1; j++)
{
if(j<weight[i])
dp[i][j]=dp[i-1][j];
else
dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
}
}
}
存储空间优化:
0-1背包中, dp[i][j]=max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i]) ,新的物品装下或者不装的价值取决于 dp[i-1][j] 和 dp[i-1][j-weight[i]] ,dp[i-1][j] 在物品索引 i 的上一次循环中已计算出,而 dp[i-1][j-weight[i]] 是 i 的上一轮循环中计算出的,且在 j 的内层循环中,若将 j 的内层循环改成 从 v 到 0 的降序顺序,则 dp[i-1][j-weight[i]] 在 dp[i][j] 之前计算,而 dp[i][j-weight] 在dp[i][j] 之后计算,则可直接用 dp[j-weight] 来代替 dp[i][j-weight] ,只用一维的数据结构 dp[v] 来存储动态规划的状态。
状态转移方程:若 j >= weight[i] ,dp[j]=max(dp[j], dp[j-weight[i]]+value[i]),若 j < weight[i],则不更新 dp[i] 。
程序如下所示:
void bag01_opt()
{
const int n; //物品种类数
const int w; //背包最大容量
int * weight[n+1]; //各物品重量
int * value[n+1]; //各物品价值
int dp[v+1]; //背包容量为v时最大价值
//i=0初始化,利用只装第一件物品的情况初始化
for(int j=0;j<=w;j++)
{
if(j<weight[0])
{
dp[j]=0;
}
else
{
dp[j]=value[0];
}
}
//将 w 维度上的循环反序,保证计算dp[j]时的dp[j-weight[i]]是i-1轮循环保留下的,而不是i轮循环保存的
//每个物品只有一件,故要保证 dp[j-weight[i]] 是i-1轮留下来的
for(int i=1;i<n+1;i++)
{
for(int j=w;j>=weight[i];j--)
{
//此处不用判断,循环已经做了控制,大于才更行dp,小于不用更新
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
}
}
}
- 完全背包
问题描述:n 种物品,每种物品有无数件,每种物品的重量为 wieght[i],每种物品的价值为 value[i] ,背包的总容量为 v ,求背包中能装入物品的最大价值总和。
完全背包按照其思路可以用一个而为数组实现状态转移方程:
f[i][j]=max{f[i-1][v-kweight[i]]+kvalue[i] | 0<=k && c[i]<=v};
与0-1背包中原理类似,可将二维数组用一维数组表示:f[j]=max{f[j],f[j-weight[i]]+value[i] | j>=weight[i]};因为每种物品的数量无限,则无需保证 dp[j-weight[i]] 是 i 的哪一轮循环,则 j 的循环可以从 0 到 v ,程序如下所示:
void bag_full()
{
const int n; //物品种类数
const int w; //背包最大容量
int * weight[n+1]; //各物品重量
int * value[n+1]; //各物品价值
int dp[v+1]; //背包容量为v时最大价值
for(int i=0;i<n+1;i++)
{
dp[i]=0;
}
//每件物品有多件,故得保证dp[j-weight[i]]是当前考虑第i轮循环产生的最新结果
for(int i=1;i<n+1;i++)
{
for(int j=0;j<V+1;j++)
{
//大于才更新dp
if(j>=weight[i])
{
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
}
}
}
}