今天下午又把 0-1 背包问题看了下,发现之前的写法虽然答案正确,但是和动态规划的思想相关度不大。不是想当然地从一个二维数组的 [0][0] 元素开始求解。
直接放上代码吧,因为已经写得很详细了。
其中 weight 是存储了每件物品重量的 vector,value 是存储了每件物品价值的 vector,c 表示背包容量。
/**
最优子结构:问题的最优解包含了其子问题的最优解
重叠子问题:用来解原问题的递归算法可以反复地解同样的子问题,而不是总在产生新的子问题。
当一个递归算法不断地调用同一问题时,我们说该最优问题包含重叠子问题
自底向上求解问题:利用最优子结构性质,递归地从子问题的最优解逐步构造出整个问题的最优解
*/
#include <iostream>
#include <vector>
using namespace std;
class Knapsack
{
public:
int knapsack(vector<int>& weight, vector<int>& value, const int c) //c表示背包容量
{
int n = weight.size(); //有n件物品
//vector<vector<int>> maxValue(n+1, vector<int>(c+1));
vector<vector<int>> maxValue;
maxValue.resize(n+1);
for(unsigned i=0; i<maxValue.size(); ++i)
{
maxValue[i].resize(c+1);
}
/*背包容量为0时,价值为0*/
for(int i=0; i<=n; ++i)
maxValue[i][0] = 0;
/*没有物品的时候,价值也为0*/
for(int i=0; i<=c; ++i)
maxValue[0][i] = 0;
//求出第一列的值,即背包容量为1
for(int i=n; i>0; --i)
{
if(weight[i-1] >= 1)
maxValue[i][1] = 0;
else
maxValue[i][1] = value[i-1];
}
//求出最后一行的值,即只有一件物品 n
for(int i=c; i>0; --i)
{
if(weight[n-1] >= i)
maxValue[n][i] = 0;
else
maxValue[n][i] = value[n-1];
}
/*maxValue[i][j] 是背包容量为j,可选物品为 i, i+1, ..., n时0-1背包问题的最优值*/
/*自底向上求解问题*/
for(int j=1; j<=c; ++j)
{
for(int i=n; i>=1; --i)
{
/*因为i是从0开始的,所以weight和value的 i-1 对应的物品其实就是 maxValue 中下标为i的物品*/
/*先让可选物品为 i-1, i, i+1, ..., n 时的最优值等于可选物品为 i, i+1, ..., n 时的最优值*/
maxValue[i-1][j] = maxValue[i][j];
/*如果当前物品的重量小于总重量,那么下一个子问题的最优值要么包含当前物品,要么不包含当前物品*/
if(weight[i-1] <= j)
{
maxValue[i-1][j] = max(maxValue[i][j], maxValue[i][j-weight[i-1]]+value[i-1]);
}
}
}
return maxValue[1][c];
}
};
int main()
{
int w[4] = {2,3,4,5};
int v[4] = {3,4,5,7};
vector<int> weight(w, w+4);
vector<int> value(v, v+4);
int c = 9;
Knapsack kp;
int maxWeight = kp.knapsack(weight, value, c);
cout << maxWeight << endl;
return 0;
}
晚上上完算法课,看到有另一种写法,好像和我之前在别人的博客上说的思想相似。回来又看了下,发现原来是对 maxValue[i][j] 的理解不同:在上面的写法中,maxValue[i][j] 被解释为 “背包容量为j,可选物品为 i, i+1, …, n时0-1背包问题的最优值”,这通常来说是一种反向思维。
而我也可以这样理解:maxValue[i][j] 是 “背包容量为j,可选物品为 1, 2, 3, …, i 时的最优值”,这样代码写起来反而更易理解。
两种方法的递归式如图:
按第二种理解时的代码如下:
#include <iostream>
#include <vector>
using namespace std;
class Knapsack
{
public:
int knapsack(vector<int>& weight, vector<int>& value, const int c)
{
int n = weight.size();
vector<vector<int>> maxValue(n+1, vector<int>(c+1));
/*背包容量为0时,价值为0*/
for(int i=0; i<=n; ++i)
maxValue[i][0] = 0;
/*没有物品的时候,价值也为0*/
for(int i=0; i<=c; ++i)
maxValue[0][i] = 0;
/*与之前不同的是,这里的 maxValue[i][j] 表示候选物品为 1, 2, ..., i, 背包容量为 j 时问题的最优值*/
for(int i=1; i<=n; ++i)
{
for(int j=1; j<=c; ++j)
{
maxValue[i][j] = maxValue[i-1][j]; //对于当前物品,可能放入也可能不放入
if(weight[i-1] <= j)
{
maxValue[i][j] = max(maxValue[i-1][j], maxValue[i-1][j-weight[i-1]] + value[i-1]);
}
}
}
return maxValue[n][c];
}
};
int main()
{
int w[4] = {2,3,4,5};
int v[4] = {3,4,5,7};
vector<int> weight(w, w+4);
vector<int> value(v, v+4);
int c = 9;
Knapsack kp;
int maxWeight = kp.knapsack(weight, value, c);
cout << maxWeight << endl;
return 0;
}