代码题(52)— 01背包问题
1、动态规划求解
问题描述:(01背包问题)有n 个物品,它们有各自的重量和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?
对于这种问题,我们可以采用一个二维数组去解决:f[i][j],其中i代表加入背包的是前i件物品,j表示背包的承重,f[i][j]表示当前状态下能放进背包里面的物品的最大总价值。那么,f[n][m]就是我们的最终结果了。
寻找递推关系式,面对当前商品有两种可能性:
第一,包的容量比该商品体积小,装不下,此时的价值与前i-1个的价值是一样的,即V(i,j)=V(i-1,j);
第二,还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{ V(i-1,j),V(i-1,j-w(i))+v(i) }
其中V(i-1,j)表示不装,V(i-1,j-w(i))+v(i) 表示装了第i个商品,背包容量减少w(i)但价值增加了v(i);
由此可以得出递推关系式:
1) j<w(i) V(i,j)=V(i-1,j)
2) j>=w(i) V(i,j)=max{ V(i-1,j),V(i-1,j-w(i))+v(i) }
#include<iostream> #include<algorithm> #include<stdio.h> #include <vector> #include<string> #include<sstream> #include<map> #include<set> #include <functional> // std::greater using namespace std; int main() { int w[] = {0,2, 3,4,5}; int v[] = {0,3,4,5,6 }; int c = 8; vector<int> x(5, 0); vector<vector<int>> res(5, vector<int>(c+1, 0)); for (int i = 1; i <= 4; ++i) { for (int j = 1; j <= c; ++j) { if (j < w[i]) //装不下 res[i][j] = res[i - 1][j]; else //能装下,但装与不装找最优 { res[i][j] = max(res[i - 1][j], res[i - 1][j - w[i]] + v[i]); } } } cout <<"the value: "<< res[4][c]<<endl; //得到最优结果 // 回溯找到最优结果的组合方式 int i = 4, j = c; while (i > 0) { if (res[i][j] == res[i - 1][j]) //相等说明没装 i--; else if (res[i][j] == res[i - 1][j - w[i]] + v[i]) { x[i] = 1; //标记已被选中 j = j - w[i]; //回到装包之前的位置 i--; } } for (int q = 1; q < x.size(); ++q) { cout << x[q] << " "; } system("pause"); return 0; }
2、空间优化
可以将V缩减成一维数组,从而达到优化空间的目的,状态转移方程转换为 B(j)= max{B(j), B(j-w(i))+v(i)};
并且,状态转移方程,每一次推导V(i)(j)是通过V(i-1)(j-w(i))来推导的,所以一维数组中j的扫描顺序应该从大到小(capacity到0),否者前一次循环保存下来的值将会被修改,从而造成错误。所以该一维数组后面的值需要前面的值进行运算再改动,如果正序便利,则前面的值将有可能被修改掉从而造成后面数据的错误;相反如果逆序遍历,先修改后面的数据再修改前面的数据,此种情况就不会出错了。
然而不足的是,虽然优化了动态规划的空间,但是该方法不能找到最优解的解组成,因为动态规划寻早解组成一定得在确定了最优解的前提下再往回找解的构成,而优化后的动态规划只用了一维数组,之前的数据已经被覆盖掉,所以没办法寻找,所以两种方法各有其优点。
#include<iostream> #include<algorithm> #include<stdio.h> #include <vector> #include<string> #include<sstream> #include<map> #include<set> #include <functional> // std::greater using namespace std; int main() { int w[] = {0,2, 3,4,5}; int v[] = {0,3,4,5,6 }; int c = 8; vector<int> x(5, 0); vector<int> res(c+1, 0); for (int i = 1; i <= 4; ++i) { for (int j = c; j >=0; --j) //从后往前遍历, { if (j - w[i] >= 0 && res[j] <= res[j - w[i]] + v[i]) res[j] = res[j - w[i]] + v[i]; } } cout <<"the value: "<< res[c]<<endl; //得到最优结果 system("pause"); return 0; }
3、完全背包问题
问题描述:设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。
问题解法其实和01背包问题一样,只是初始化的值和递推公式需要稍微变化一下。初始化时,当只考虑一件物品a时,f[1][j] = j/weight[a]。 递推公式计算时,
f[i][y] = max{f[i-1][y], (f[i][y-weight[i]]+value[i])},注意这里当考虑放入一个物品 i 时应当考虑还可能继续放入 i,因此这里是f[i][y-weight[i]]+value[i], 而不是f[i-1][y-weight[i]]+value[i]。