0-1背包问题
分析:同样,若没有思路可以考虑暴力循环的方法,即先找到多少种情况(2n),再每一种情况是否符合要求并求价值,求取最大值,O((2n)*n)。
若进一步考虑,可定义表达式:F(n,c),意思为将n个物品装入容量为c的背包中,能取得的最大值。如下图所示,采用递归的方式可以避免暴力。对于第n个物品,可以有两种取法,取(1)或不取(0),当取的时候,则最大值为v(n)+F(n-1,c-wn),即将n-1个物品装入c-wn的容量中,所能得到的最大价值;当不取的时候,则为F(n-1,c),因为没取n,故容量不变。那么,若采取递归计算的话,实际上每层求max(v(n)+F(n-1,c-wn),F(n-1,c)),并返回给最上层即可。
#include "pch.h"
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 递归法解决0-1背包问题
class Solution
{
public:
// index为第几个物品,c为当前容量,
int getMaxValue( vector<int> &weight, vector<int> &value, int index, int c)
{
if (index < 0 || c <= 0) return 0;
int res = 0;
// 两种情况,取index和不取index,并求最大值
if (weight[index] <= c) res = value[index] + getMaxValue(weight, value, index-1, c - weight[index]);
res = max(res, getMaxValue(weight, value, --index, c));
return res;
}
};
int main()
{
vector<int> weight = { 2,1,3,2 };
vector<int> value = { 12,10,20,15 };
Solution s;
cout<<(s.getMaxValue(weight, value, weight.size() - 1, 5)); // 37
return 0;
}
可采用记忆化搜索对气优化
#include "pch.h"
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 递归法解决0-1背包问题
class Solution
{
public:
// index为第几个物品,c为当前容量,
int getMaxValue( vector<int> &weight, vector<int> &value, int index, int c, vector<vector<int> > &memo)
{
if (index < 0 || c <= 0) return 0;
// 1、
if (memo[index][c]) return memo[index][c];
int res = 0;
// 两种情况,取index和不取index,并求最大值
if (weight[index] <= c) res = value[index] + getMaxValue(weight, value, index-1, c - weight[index],memo);
res = max(res, getMaxValue(weight, value, index-1, c, memo));
// 2、
memo[index][c] = res;
return res;
}
};
int main()
{
vector<int> weight = { 2,1,3,2 };
vector<int> value = { 12,10,20,15 };
// 3、
vector<vector<int> > memo(weight.size(), vector<int>(6, 0));
Solution s;
cout<<(s.getMaxValue(weight, value, weight.size() - 1, 5, memo));
return 0;
}
进一步,由于该递归中存在最优子结构,故可从下到上采用动态规划的办法。可参考机器走路问题82。不过本题中二维条件不够明显,需进一步分析。首先,题中要求,n个物品最多可装入容量为c的背包的最大价值,写成表达式为F(n,c),那么这是一个二维函数,将82中的竖坐标当作n,横坐标当作容量c,那么F(i,ci)代表前i个物品装入ci背包中能得到的最大价值。。接下来需要找状态转移方程,由于背包可装可不装,那就分情况讨论,若i装,则F(i,ci) = vi+f(i-1,ci-wi):即第i个物品的价值,加上,前i-1个物品装入ci-wi背包中的最大价值。若i不装入,则F(i,ci) = F(i-1,ci):即前i-1个物品装入ci背包中的最大价值。故状态转移方程为F(i,ci) = max(vi+f(i-1,ci-wi),F(i-1,ci))。
#include "pch.h"
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 动态规划解决0-1背包
int main()
{
vector<int> weight = { 2,1,3,2 }; // 物品重量
vector<int> value = { 12,10,20,15 }; // 物品价值
// 当成二维图形,横坐标为容量,纵坐标为物品
int c = 5;
vector<vector<int> > dp(weight.size(), vector<int>(c+1, 0));
// 第一列不用赋初值,因为第一列的容量都为0,故不可能放入物品
// 先将第一行赋初值
for (int i = 0; i <= 5; i++)
{
dp[0][i] = weight[0] <= i ? value[0] : 0;
}
for (int i = 1; i < weight.size(); i++)
{
for (int j = 1; j <= 5; j++)
{
if (weight[i] <= j) dp[i][j] = value[i] + dp[i - 1][j - weight[i]];
dp[i][j] = max(dp[i-1][j], dp[i][j]);
}
}
cout << dp[weight.size()-1][5];
return 0;
}
动态规划的优化基本上均是尽可能降低空间复杂度。通常是二维变一维,一维变变量。
1、从上述循环中可以看到,每次循环只用到了两行数据,故可用求余进行处理。
2、再进一步分析,可从右向左进行刷新,这样只保存一行数据就可不断求得值
// 1、变为两行数组
// 动态规划解决0-1背包
int main()
{
vector<int> weight = { 2,1,3,2 }; // 物品重量
vector<int> value = { 12,10,20,15 }; // 物品价值
// 当成二维图形,横坐标为容量,纵坐标为物品
int c = 5;
// 优化1、
vector<vector<int> > dp(2, vector<int>(c+1, 0));
// 第一列不用赋初值,因为第一列的容量都为0,故不可能放入物品
// 先将第一行赋初值
for (int i = 0; i <= 5; i++)
{
dp[0][i] = weight[0] <= i ? value[0] : 0;
}
for (int i = 1; i < weight.size(); i++)
{
for (int j = 1; j <= 5; j++)
{
// 优化2、
if (weight[i] <= j) dp[i % 2][j] = value[i] + dp[(i - 1) % 2][j - weight[i]];
dp[i % 2][j] = max(dp[(i-1) % 2][j], dp[i % 2][j]);
}
}
// 优化3、
cout << dp[(weight.size()-1) % 2][5];
return 0;
}
// 动态规划解决0-1背包
int main()
{
vector<int> weight = { 2,1,3,2 }; // 物品重量
vector<int> value = { 12,10,20,15 }; // 物品价值
// 当成二维图形,横坐标为容量,纵坐标为物品
int c = 5;
// 优化1、
vector<int> dp(c+1, 0);
for (int i = 0; i <= 5; i++)
{
dp[i] = weight[0] <= i ? value[0] : 0;
}
for (int i = 1; i < weight.size(); i++)
{
// 逆序考虑,则不会覆盖数据
for (int j = c; j >= 0; j--)
{
int tmp = dp[j];
// 优化2、
if (weight[i] <= j) dp[j] = value[i] + dp[j - weight[i]];
dp[j] = max(dp[j], tmp);
}
}
// 优化3、
cout << dp[c];
return 0;
}