0-1背包问题:
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
这个问题的特点是:每种物品只有一件,可以选择放或者不放。
算法基本思想:
利用动态规划思想 ,子问题为:f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。
其状态转移方程是:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]} //这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。
解释一下上面的方程:“将前i件物品放入容量为v的背包中”这个子问题,如果只考虑第i件物品放或者不放,那么就可以转化为只涉及前i-1件物品的问题,即1、如果不放第i件物品,则问题转化为“前i-1件物品放入容量为v的背包中”;2、如果放第i件物品,则问题转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”(此时能获得的最大价值就是f [i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i])。则f[i][v]的值就是1、2中最大的那个值。
(注意:f[i][v]有意义当且仅当存在一个前i件物品的子集,其费用总和为v。所以按照这个方程递推完毕后,最终的答案并一定是f[N] [V],不一定是唯一的最大值,也可以是f[N][0..V]的最大值。
以上概念和算法基本思想来源于blog:http://www.cnblogs.com/fly1988happy/archive/2011/12/13/2285377.html
以下为算法代码:是个不断优化的过程。首先(1)给出了递归和迭代的思想,(2)然后对迭代的算法代码进行空间优化。(3)然后给出了该问题的变种:恰好装满背包和完全背包问题。
(1)递归方法:
// 0-1 背包问题递归方法 回溯求最佳解
int packageRecursion(int c, int n)
{
if(n == N)
return (c < weight[n])? 0 : value[n];
if(c < weight[n])
return packageRecursion(c, n+1);
return max(packageRecursion(c, n+1), packageRecursion(c - weight[n], n+1) + value[n]);
}
(2)迭代方法:
// 0-1 背包问题迭代方法
void packageIteraction()
{
int **F;
F = new int*[N+1]; // 申请内存空间
int i;
for(i = 0; i < N+1; i++)
{
F[i] = new int [C+1];
}
int j;
for(i = 0 ; i < N+1; i++)
{
for(j = 0; j < C+1; j++)
{
F[i][j] = 0; // 初始化
}
}
// 正好装满背包 加上这几行
/*for(i = 0 ; i < N+1; i++)
{
for(j = 1; j < C+1; j++)
{
F[i][j] = min;
}
}*/
for(i = 1; i < N+1; i++)
{
for(j = weight[i]; j < C+1; j++)
{
F[i][j] = F[i-1][j] > (F[i-1][j-weight[i]]+value[i])? F[i-1][j] : (F[i-1][j-weight[i]]+value[i]);
}
}
if(F[N][C] > 0)
{
cout << "the opt value:" << F[N][C] << endl;
j = C; // 回溯 遍历出所选择的节点
for(i = N; i >= 1; i--)
{
if(F[i][j] == (F[i-1][j-weight[i]]+value[i]))
{
cout << i << "weight=: " <<