有n件物品和一个容量为v的背包,求能够获得的最大价值。其中重量是w[i],价值是v[i]。ios
例 4 5数组
1 2函数
2 3优化
3 4spa
2 2.net
01背包问题的经常使用的f[v]=max(f[v],f[v-w[i]]+v[i])的解法是由树递归得来的,这里从头推演一遍。3d
咱们能够这样理解,咱们从第一个物品开始,每一个物品都有两种状况:选或不选,因而就有下图:code
今后咱们能够设函数res(int i,int j)(i表示第几个物品,j表示背包剩余容量),不断的判断选仍是不选,不断地递归调用,直到i=n+1时,就开始回溯,逐层返回获得的最大价值,最终得到背包能装下的最大价值。blog
由此咱们能够写出递归求解的代码:递归
#include<cstdio> #include<iostream> #include<algorithm> #include<string.h> using namespace std; const int maxn=101; int w[maxn],v[maxn],n,w1; int res(int i,int j) { int ans=0; //保存每一步求得的当前最大价值 if(i==n+1) return ans; // 当到n+1个物品时就开始回溯 if(w[i]>j) ans=res(i+1,j); //若当前物品的重量大于背包容量时,就没法放入背包 else ans=max(res(i+1,j),res(i+1,j-w[i])+v[i]);//若能放下,则取放入或不放入两种状况中较大值 return ans; } int main() { scanf("%d%d",&n,&w1); for(int i=1;i<=n;i++) scanf("%d%d",&w[i],&v[i]); cout<<res(0,w1)<<endl; return 0; }
可是,咱们能够知道这样求得复杂度O(n)=2^n,此时已经超时了,因此咱们须要优化代码。
咱们从上图中能够发现,在每一步都有不少重复调用的过程(用红笔画出的格子都是须要重复调用的),因此这样会有不少的重复的计算,咱们能够发现出去相同的在每一层最多会有v+1个,由于就只能会有res(i,5)、res(i,4)、res(i,3)、res(i,2)、res(i,1)、res(i,0)这几种状况,因此除去相同的以后的复杂度为n*v,复杂度大大下降。
咱们能够经过引入dp数组来保存计算过的结果,而后每次调用时都判断该状态是否已经被计算过了,若计算过了就能够直接返回结果。
此时的代码为:
#include<cstdio> #include<iostream> #include<algorithm> #include<string.h> using namespace std; const int maxn=101; const int maxw=10003; int w[maxn],v[maxn],n,w1,dp[maxn][maxw]; //引入dp数组保存已经计算过的结果 int res(int i,int j) { //判断这种状况是否被计算过,若被计算过就能够直接返回结果不用进行下面的一系列判断计算 if(dp[i][j]>0) return dp[i][j]; int ans=0; if(i==n) return ans; if(w[i]>j) ans=res(i+1,j); else ans=max(res(i+1,j),res(i+1,j-w[i])+v[i]); dp[i][j]=ans; //保存求出结果 return ans; } int main() { memset(dp,-1,sizeof(dp)); scanf("%d%d",&n,&w1); for(int i=0;i<n;i++) scanf("%d%d",&w[i],&v[i]); cout<<res(0,w1)<<endl; return 0; }
咱们由此又能够得出打表的方法即:
for i = 1.......n
for j =1....v
dp[i][j]=max(dp[i-1][ j ] ,dp[ i-1 ][ j-w[ i ] ] + v[ i ]);
再对空间复杂度进行优化可得:
for i = 1.......n
for j =v....p[i]
dp[ j ]= max( dp[ j ] ,dp[ j-w[ i ] ] + v[ i ]);
所以就获得了咱们如今经常使用的计算01背包的方法。
01背包 例题:http://www.javashuo.com/article/p-adqbhflh-kz.html(不须要刚好装满)
咱们还应该注意初始化的细节问题:
咱们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。 有的题目要求“刚好装满背包”时的最优解,有的题目则并无要求必须把背 包装满。一种区别这两种问法的实现方法是在初始化的时候有所不一样。 若是是第一种问法,要求刚好装满背包,那么在初始化时除了F[0]为0,其 它F[1..V ]均设为−∞,这样就能够保证最终获得的F[V ]是一种刚好装满背包的 最优解。 若是并无要求必须把背包装满,而是只但愿价格尽可能大,初始化时应该 将F[0..V ]所有设为0。
这是为何呢?能够这样理解:初始化的F数组事实上就是在没有任何物 品能够放入背包时的合法状态。若是要求背包刚好装满,那么此时只有容量 为0的背包能够在什么也不装且价值为0的状况下被“刚好装满”,其它容量的 背包均没有合法的解,属于未定义的状态,应该被赋值为-∞了(即控制在背包没有装满时是不合法的)。若是背包并不是 必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的 价值为0,因此初始时状态的值也就所有为0了(在背包没有被装满也是合法的)。