01背包问题是经典的动态规划问题,问题描述如下:
我们用f[i][j]表示将前i个物品放入容量为j的背包里所能获得的最大价值,则状态转移方程如下:
具体程序如下:
#include "stdafx.h"
#include<iostream>
#include<vector>
using namespace std;
int maxValue(vector<int>weight,vector<int>value,int bagCapacity)
{
int m=weight.size();
int n=bagCapacity;
vector<vector<int>>dp(m+1,vector<int>(n+1,0));
vector<int> rollDp(n+1,0);
for(int i=0;i<m;++i)
{
for(int j=n-1;j>=0;--j)
{
if(weight[i]<=j+1)
{
dp[i+1][j+1]=max(dp[i][j+1],dp[i][j+1-weight[i]]+value[i]); //当内循环从右到左时,可以用滚动数组来优化空间复杂度。
rollDp[j+1]=max(rollDp[j+1],rollDp[j+1-weight[i]]+value[i]); //用于滚动数组降低空间复杂度
}
else
{
dp[i+1][j+1]=dp[i][j+1];
}
}
}
cout<<"滚动数组所得结果为: "<<rollDp[n]<<endl; //滚动一维数组输出结果和二维数组一致
return dp[m][n];
}
int main()
{
int numCnt=0; //物品数目
int bagCapacity=0; //背包容量
while(cin>>numCnt>>bagCapacity)
{
vector<int>weight(numCnt,0);
vector<int>value(numCnt,0);
for(int i=0;i<numCnt;++i)
{
cin>>value[i];
}
for(int i=0;i<numCnt;++i)
{
cin>>weight[i];
}
cout<<"最大价值为: "<<maxValue(weight,value,bagCapacity);
}
}
程序运行结果如下:
上述输入第一行第一个数代表物品个数,第二个数代表背包最大容量;第二行每个数分别代表对应的物品的价值,第三行每个数分别代表对应物品的重量。
从程序中我们可以看到如果利用二维数组dp来编写程序,就是完全按照我们之前写的状态转移方程来写,这个很好理解,但是空间复杂度为O(N*W)。我们注意到状态转移方程中dp[i][j]只与dp[i-1][]有关(第二维空着不是笔误哦,我是想表达的是外层的第i个循环的值,只与外层的第i-1个循环的值决定,这样一来,内层循环不管顺序还是逆序都可以,自己思考一下为什么?),而当内层循环逆序的时候,我们通常可以用一维数组来代替二维数组,原因如下:这种情况下我们可以用dp[j]取代dp[i][j]是因为计算dp[i][j]前,我们已经计算了dp[i-1][j],所以如果我们之前是用的一维数组dp[j]的话,那计算dp[i][j]前一瞬间,dp[j]代表的是dp[i-1][j],dp[i][j]计算完后,dp[j]表示的就是dp[i][j],程序中我将这两种方法写到了一起,方便领悟其中的差别。现在我们思考一个问题,为什么用一维数组的时候,内层循环必须是逆序,不能顺序?原因是
dp[i+1][j+1]=max(dp[i][j+1],dp[i][j+1-weight[i]]+value[i]),注意到式中有dp[i][j+1-weight[i]],如果假设j+1-weight[i]=3,那当我们求dp[i+1][j+1],而j=6时,此时用二维数组肯定不会有问题,但是用一维数组且内循环顺序增长时,此时dp[j+1]即dp[7]可能与dp[3]有关,但此时的dp[3]已经是dp[i+1][3]了,而不再是dp[i][3],违背了我们的状态转移方程。