一:01背包问题
1.使用二维数组
以上面的数据为例进行讨论:
核心就是需要3个数组
f [i][j] 表示前 i 个物品 在最大容量为 j 的背包中的最大价值
val [i] 表示第 i 个物品的价值
wei [i] 表示第 i 个物品的重量
需要一个二重循环依次遍历 i j
i 表示前 i 个物品的
j 表示最大容量
即在 i = 1时:通过循环 j 找出背包容量分别为 1 2 3 4 5时的背包的最大价值
当 i = 2 3 4时:
最后得到 8
拿f【3】【5】为例:
现在面临的问题是要不要把第3个物品放进背包:
如果w【3】> j 那肯定是放不下的
如果w【3】<= j 就需要判断要不要放:
如果放:就要求出在 i-1 j- w【3】的情况下的最大价值再加上v【3】最后的结果就是如果放第三个物品的最大价值
如果不放:那就还是 f【i-1】【j】
把这两种情况都算出来取最大值就是f【i】【j】的最大价值
最后求出的f【N】【V】即答案
代码如下:
#include <iostream>
using namespace std;
int max(int a,int b)
{
if(a>=b) return a;
else return b;
}
int main()
{
int N,V;
cin>>N>>V;
int v[1001],w[1001];
int dp[1001][1001];//dp[i][j]表示i件物品的在容量为j的背包的最大价值
for( int i = 1;i <= N;i++)
{
cin>>w[i]>>v[i];
}
for(int i = 1;i <= N;i++)
for(int j = 1;j <= V;j++)//j表示的是背包的容量
{
if(w[i] <= j)//够放
{
dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
}
else//不够放
{
dp[i][j] = dp[i-1][j];
}
}
cout<<dp[N][V];
return 0;
}
2.使用一维数组
我们可以思考,二维数组的时候
f [ i ] [ j ] 只和它的上一行有关系
dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
所以这会造成空间的浪费,所以我们可以只用一个一维数组
然后不断覆盖和更新它
定义一个一维数组:v 【N】
按照之前的步骤赋值:
2 2 2 2 2
注意这里的f【】数组需要逆序遍历
f 【j】= 以前的 f 【i】【j】
j之前的j
f【j】= f【i-1】【j】
所以对于
f[i][j] = max(f[i-1][j],f[i-1][j-w[i]]+v[i]);
即:
f [j] = max(f[j],f[j-w[i]]+v[i])
仔细思考为什么:
f【j】= f【i-1】【j】
f【j-w【i】】 = f【i-1】【j-w【i】】
#include <iostream>
using namespace std;
int max(int a,int b)
{
if(a>=b) return a;
else return b;
}
int main()
{
int N,V;
cin>>N>>V;
int v[1001],w[1001];
int f[1001] = {0};//dp[i][j]表示i件物品的在容量为j的背包的最大价值
for( int i = 1;i <= N;i++)
cin>>v[i]>>w[i];
for(int i = 1;i <= N;i++)
for(int j = V;j >= v[i];j--)//j表示的是背包的容量
f[j] = max(f[j],f[j-v[i]]+w[i]);
cout<<f[V];
return 0;
}
这里我出现了一个很低级的错误:
我在创建f数组的时候实在函数内部创建的局部变量,需要给他初始为 0;不然数字是随机的,会出现错误
3.简化
还有一个简化的代码就是不创建w和v数组,而是再每个循环中输入,这样的话也可以节省空间
二:完全背包问题
就是每个物品可以用多次,不再是只能用一次
所以再双重循环里加一个for循环用来计算这个物品需要用几次
for(int i = 1 ; i<=n ;i++)
for(int j = 0 ; j<=m ;j++)
{
for(int k = 0 ; k*v[i]<=j ; k++)
f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
仔细地思考一下核心代码
思考完成后看下一个问题就是三重循环,时间复杂度太高,在做题时会超时,所以思考怎么优化一下使时间复杂度减低
如何让时间复杂度降低的思想和道理我还没搞懂,但是结论非常重要
记住吧!
//01背包:
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
//完全背包:
f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
//两者其实就只有一个下标的差距
二维数组的形式:
for(int i = 1 ; i<=n ;i++)
for(int j = 0 ; j<=m ;j++)//这里j一定是从v[i]开始循环,因为如果j小于v[i]根本放不进去
{
//f[i][j] = max(f[i-1][j],f[i][j-v[i]]+w[i]);
if(j >= v[i])
f[i][j] = max(f[i-1][j],f[i][j-v[i]]+w[i]);
else f[i][j] = f[i-1][j];
}
j从0开始循环,而不是从v【i】
这里解释一下:
按照常理来说就是应该从0开始循环找出每个数组的值
但是为什么一维数组的时候是从v【i】到V呢??这是因为一维数组第二轮更新的时候到v【i】就会停止,v【i】前面不需要更新,因为他储存的就是上一轮的f【i-1】【j】
综上:
二维数组j的遍历范围:0-V
一维数组j的遍历范围:v【i】-V
一维数组的形式:
与01背包的核心一样:(注意 j 是从小到大,01背包是从大到小)
for(int i = 1 ; i<=n ;i++)
for(int j = v[i] ; j<=m ;j++)
{
f[j] = max(f[j],f[j-v[i]]+w[i]);
}
总结:(01背包与完全背包的不同)
1.二维数组的形式:有一个下标的差距
2.一维数组的形式:j 的升序和降序