背包问题无外乎是利用动态规划的思想求解。而求解动态规划问题最主要是寻找到核心推导式(有些问题隐藏的非常深,一眼很难看出来)。这里不再介绍动态规划的思想,主要讲解背包问题,闲话少说,直接上题。
1、0-1背包
问题描述:有编号分别为a,b,c,d,e的五件物品,它们的重量weight分别是2,2,6,5,4,它们的价值value分别是6,3,5,4,6,每件物品数量只有一个,现在给你个承重为C=10的背包,如何让背包里装入的物品具有最大的价值总和?
利用动态规划思想:首先生成一个N*(C+1)的二维数组,N是物品数,C是背包容量。
i、设weight[i]为第i+1件物品的体积,value[i]为第i+1件物品产生的价值
ii、其dp[i][w]的含义是只考虑前i+1件物品(无所谓是否在背包中)在背包容量为w下,可获得的最大价值收益。
寻找其核心式:dp[i][w]=max{dp[i-1][w],dp[i-1][w-weight[i]]+value[i]},其含义如下:
a、首先分析dp[i-1][w-weight[i]]+value[i],考虑前i件物品下,再添加第i+1件物品(确保总体积小于w)产生的总价值;
b、其次分析dp[i-1][w],考虑前i件物品在体积w下,产生的总价值;
c、最后取两者的最大值作为考虑前i+1件物品(无所谓是否全在背包中)产生的总价值。
name | weight | value | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
a | 2 | 6 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
b | 2 | 3 | 0 | 0 | 6 | 6 | 9 | 9 | 9 | 9 | 9 | 9 | 9 |
c | 6 | 5 | 0 | 0 | 6 | 6 | 9 | 9 | 9 | 9 | 11 | 11 | 14 |
d | 6 | 4 | 0 | 0 | 6 | 6 | 9 | 9 | 9 | 10 | 11 | 13 | 14 |
e | 4 | 6 | 0 | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 | 15 | 15 |
#include<vector>
#include<algorithm>
using namespace std;
int knapsack0_1(vector<int>& value, vector<int>& weight, int& C) {
int row = value.size();
vector<vector<int>> dp(row, vector<int>(C + 1, 0));
//先单独考虑第1件物品是否存入背包中,对dp的第一行进行赋值
for (int w = 0; w < C + 1; ++w) {
if (w >= weight[0])
dp[0][w] = value[0];
}
//再考虑前i件物品是否存入背包
for (int i = 1; i < row; ++i) {
for (int w = 0; w < C + 1; ++w) {
if (w - weight[i] >= 0) {
dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weight[i]] + value[i]);
}
else
dp[i][w] = dp[i - 1][w];
}
}
return dp[row-1][C];
}
2、完全背包(即每件物品数无限个,这是与0-1背包问题的最大不同)
问题描述:有编号分别为a,b,c,d的四件物品,它们的重量weight分别是2,3,4,7,它们的价值value分别是1,3,5,9,每件物品数量无限个,现在给你个承重C为10的背包,如何让背包里装入的物品具有最大的价值总和?
解法与01背包问题类似,只是在对二维数组dp的初始化及递推公式上需要稍微变化一下。
对二维数组dp的第一行初始化时,dp[0][w]=w<weight[0]?0:(w/weight[0]*value[0]);
推导式:dp[i][w]=max{dp[i-1][w],dp[i][w-weight[i]]+value[i]},注意这里应当考虑放入一个物品 i 时应当考虑还可能继续放入 i,因此这里是dp[i][w-weight[i]]+value[i], 而不是dp[i-1][w-weight[i]]+value[i]。
name | weight | value | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
a | 2 | 1 | 0 | 0 | 1 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 5 |
b | 3 | 3 | 0 | 0 | 1 | 3 | 3 | 4 | 6 | 6 | 7 | 9 | 9 |
c | 4 | 5 | 0 | 0 | 1 | 3 | 5 | 5 | 6 | 8 | 10 | 10 | 11 |
d | 7 | 9 | 0 | 0 | 1 | 3 | 5 | 5 | 6 | 9 | 10 | 10 | 12 |
int knapsackAll(vector<int>& value, vector<int>& weight, int& C) {
int row = value.size();
vector<vector<int>> dp(row, vector<int>(C + 1, 0));
//先单独考虑第1物品a是否存入背包中,对dp的第一行进行赋值
for (int w = 0; w < C + 1; ++w) {
dp[0][w] = w<weight[0]?0:w/weight[0]*value[0];
}
//再考虑前i件物品是否存入背包(存在着数量无限次)
for (int i = 1; i < row; ++i) {
for (int w = 0; w < C + 1; ++w) {
if (w - weight[i] >= 0) {
dp[i][w] = max(dp[i-1][w], dp[i][w - weight[i]] + value[i]);
}
else
dp[i][w] = dp[i - 1][w];
}
}
return dp[row - 1][C];
}
3、多重背包问题(给定了每件物品的数量:多重背包中每个物品的个数都是给定的,可能不是一个,绝对不是无限个。)
问题描述:有编号分别为a,b,c的三件物品,它们的重量weight分别是1,2,2,它们的价值value分别是6,10,20,他们的数目count分别是10,5,2,现在给你个承重C为 8 的背包,如何让背包里装入的物品具有最大的价值总和?
解法一:
对二维数组dp的第一行初始化时,当w/weight<=count[0]时,dp[0][w]=w<weight[0]?0:(w/weight[0]*value[0]),当w/weight>count[0]时,dp[0][w]=count[0]*value[0]。
推导式:dp[i][w]=max{dp[i-1][w],dp[i-1][w-k*weight[i]]+k*value[i]},其中dp[i-1][w]是指第i件物品要么一件也不放置,要么第 i 件物品放 k 件,其中 1 <= k <= (w/weight[i])&&k<=count[i],考虑这一共 k+1 种情况取其中的最大价值即为dp[i][w]的值。两者取其中最大值。这里为什么不能像完全背包一样直接考虑dp[i][w-weight[i]]+value[i]呢?因为这样不容易判断第 i 件物品的个数是否超过限制数量 count[i]。
name | weight | value | count | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
a | 1 | 6 | 10 | 0 | 6 | 12 | 18 | 24 | 30 | 36 | 42 | 48 |
b | 2 | 10 | 5 | 0 | 6 | 12 | 18 | 24 | 30 | 36 | 42 | 48 |
c | 2 | 20 | 2 | 0 | 6 | 20 | 26 | 40 | 46 | 52 | 58 | 64 |
int knacksackNotAll(vector<int>& value, vector<int>& weight, vector<int>& count, int& C) {
int row = value.size();
vector<vector<int>> dp(row, vector<int>(C + 1, 0));
//先单独考虑第1物品a是否存入背包中,对dp的第一行进行赋值
for (int w = 0; w < C + 1; ++w) {
if (w / weight[0] <= count[0])//对物品数有限制
dp[0][w] = w < weight[0] ? 0 : (w / weight[0] * value[0]);
else
dp[0][w] = count[0]*value[0];
}
//再考虑前i件物品是否存入背包(存在着数量限制次数问题)
for (int i = 0; i < row; ++i) {
for (int w = 0; w < C + 1; w++) {
if (w >= weight[i]) {
int k = 0;
while (w >= (k+1)*weight[i]&&(k+1)<=count[i])//此条件限制了1<=k<=w/weight[i] && k<=count[i]
k++;
dp[i][w] = max(dp[i - 1][w], dp[i-1][w - k*weight[i]] + k*value[i]);
}
else {
dp[i][w] = dp[i - 1][w];
}
}
}
return dp[row - 1][C];
}
解法二:
由于0-1背包的物品可以是相同的,于是多重背包问题可以拆成多个物品,看成是多个物品的0-1背包问题。如上题题干,可以转换成有10+5+2=17件物品,其重量分别是1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2。其物品对应价值分别是6,6,6,6,6,6,6,6,6,6,10,10,10,10,10,20,20。承重背包为8,求装入物品具有最大价值的总和?
//先写出0-1背包问题求解函数
int knapsack0_1(vector<int>& value, vector<int>& weight, int& C) {
int row = value.size();
vector<vector<int>> dp(row, vector<int>(C + 1, 0));
//先单独考虑第1件物品是否存入背包中,对dp的第一行进行赋值
for (int w = 0; w < C + 1; ++w) {
if (w >= weight[0])
dp[0][w] = value[0];
}
//再考虑前i件物品是否存入背包
for (int i = 1; i < row; ++i) {
for (int w = 0; w < C + 1; ++w) {
if (w - weight[i] >= 0) {
dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weight[i]] + value[i]);
}
else
dp[i][w] = dp[i - 1][w];
}
}
return dp[row - 1][C];
}
//将多重背包问题转换成0-1背包问题,再利用0-1背包函数求解
int knapsackNotAllOtherWay(vector<int>& value, vector<int>& weight, vector<int>& count, int& C) {
vector<int> value1;
vector<int> weight1;
for (int i = 0; i < count.size(); ++i) {
for (int j = 0; j < count[i]; ++j) {
value1.push_back(value[i]);
weight1.push_back(weight[i]);
}
}
return knapsack0_1(value1, weight1, C);//从而转换成求解0-1背包问题
}
参考链接:
1、https://blog.csdn.net/na_beginning/article/details/62884939
2、http://www.cnblogs.com/fengty90/p/3768845.html
3、http://blog.csdn.net/mu399/article/details/7722810