由于最近是清明节比较闲,而且一直对动态规划的问题比较头疼,动态规划是一个很重要的概念,因此这个清明节我决定重新看动态规划,希望有新的认识。那么我就从网上流传比较广的背包九讲入手,希望能在清明节的假期之内更新九篇,用我自己的话语来解释,希望大家看了我写的之后能够有所收获。
接下来进入正题: 01背包问题
01背包问题是背包问题里面可以说是最简单的一个问题,先来介绍一下题目:背包承重为W,有N个物品,每个物品分别为w[1]...w[n]重量,并且每个物品都有自己的价值v[i]...v[n],求背包能放入的最大价值。
这里我就不解释为什么贪心法无法解这类问题,直接开始分析。
首先我们可以假设每个物品最后都会都有一个状态,是否放入背包。
那么动态规划最重要的是转移方程,写出转移方程也就代表这道题目已经解出来了。我们假定f[i][w]表示对于前i个物品,背包承重为w的时候能够得到的最大价值。
我们可以有 f[i][w] = max( f[i-1][w] , f[i-1][w-w[i]]+v[i] )
那么这个方程如何得到呢,前面我提到了一个前提:每个物品最后都会都有一个状态 即 是否放入背包。那么f[i-1][w]就代表i物品不放入背包,那么最大价值就是将前i-1个物品放入背包的最大价值,相对的f[i][w-w[i]]+v[i]就代表如果i放入背包的最大价值。这样一来我们就得到了伪代码:
for i 1...N
for w 1...W
f[i][w] = max( f[i-1][w], f[i][w-w[i]]+v[i] )
那么我们这个算法的时间复杂度为O(W*N),空间复杂度也为O(W*N)
对于一个程序,我们不仅需要优化时间,还需要优化空间,那么很显然的是,对于时间复杂度我们不太可能再优化,于是我们来看看空间复杂度能否优化。答案是肯定的
转移方程 f[w] = max( f[w],f[w-w[i]]+v[i] )
复杂度为O(W)。
接下来给出源程序,空间复杂度分别为O(N*W) ,O(W):
O(N*W):
#include <iostream>
using namespace std;
int w[100],v[100];
int f[101][101];
int main(){
int W,N;
cin >> W >> N;
for( int i = 1; i <= N; ++i ){
cin >> w[i] >> v[i];
}
for( int i = 1; i <= N; ++i ){
for( int j = 0; j <= W; ++j ){
if( j >= w[i] )
f[i][j] = f[i-1][j] >= f[i-1][j-w[i]]+v[i] ? f[i-1][j] : f[i-1][j-w[i]]+v[i];
else
f[i][j] = f[i-1][j];
}
}
cout << f[N][W];
return 0;
}
O(W):
#include <iostream>
using namespace std;
int w[100],v[100];
int f[101];
int main(){
int W,N;
cin >> W >> N;
for( int i = 1; i <= N; ++i ){
cin >> w[i] >> v[i];
}
for( int i = 1; i <= N; ++i ){
for( int j = W; j >= 0; --j ){
if( j >= w[i] )
f[j] = f[j] >= f[j-w[i]]+v[i] ? f[j]:f[j-w[i]]+v[i];
}
}
cout << f[W];
return 0;
}
这里如果我们需要打印出我们选择的是哪些物品,那么应该如何修改程序呢?有两种方法,第一种是加数组,第二种是倒推。我就来讲一下思路吧。
加数组:
当加入i这个物品就能够使得容量为w的背包价值最大,则将该物品加入另外的数组w中(当然,需要将之前i-1物品的状态记录下来)那么,最后W中就是所有的物品。
逆推:
如果第i个物品不放入,则f[i][W]==f[i-1][W],那么根据这个条件,我们就可以反推,得到物品i是否加入了背包。
时间与空间,常常会像这样无法兼得。
代码我就不实现了,比较简单,只需在程序上稍加修改即可。
最后写完这篇发现用了一天多,看来清明的假期无法让我完成9篇了,那希望这学期能够完成吧。国际惯例,壁纸一张,这回是汽车。