目录
算法分析
算法的时间复杂度无论是在程序竞赛还是考试面试中都是不可忽略的重要部分。
而DP就能很好的优化某些算法,就比如我们常见的递归算法的时间可以参考一下以下链接详情:http://t.csdn.cn/Puw3a
今天讲的DP主要源自于对POJ中背包问题的一个更深的理解
背包问题题目:
这个问题要如何就解决的好呢?不妨先用最朴素的方法,针对每个物体是否放入背包进行搜索试试看。这个想法实现的代码如下:
//输入
int n ,w;
int w[MAX_N],V[MAX_W];
//从第i个物品挑选总重小于j的部分
int rec(int i,int j){
int res;
if(i==n){
//已经没有剩余物品可以挑选了
res= 0;
}
else if(j<w[i]){
//背包容量不够,无法挑选这个物品
res=rec(i+1,j);
}
else{//背包容量足够,挑选和不挑选的情况各自试一下
res=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]);
}
return res;
}
void solve(){
printf("%d\n",rec(0,W));
}
这种方法固然可取,但是由于这种方法搜索的深度是n,最坏的情况就是每一层的搜索需要两次分支,然后就需要O( 2^n),当n比较大的时候就无法求解了,为了更好的理解题目以及上面出现的最坏情况,不妨看看以下递归调用的样例图:
显然,由上图所示:rec调用了两次参数(3,2),由于参数相同,那么返回的结果肯定也相同,于是第二次调用的时候明显就是浪费时间了,所以我们需要做的就是把第一次计算的结果记录下来,省略掉第二次以后重复的计算,代码如下:
//记忆化数组dp
int dp[MAX_N +1][MAX_W+1];
//从第i个物品挑选总重小于j的部分
int rec(int i,int j){
//已经计算过的话就直接用之前的结果就可以了
if(dp[i][j]>=0){
return dp[i][j];
}
int res;
if(i==n){
//已经没有剩余物品可以挑选了
res= 0;
}
else if(j<w[i]){
//背包容量不够,无法挑选这个物品
res=rec(i+1,j);
}
else{//背包容量足够,挑选和不挑选的情况各自试一下
res=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]);
}
//将结果记录到数组中
return dp[i][j]=res;
}
void solve(){
memset (dp,-1,sizeof(dp));
printf("%d\n",rec(0,w));
}
这看似微小的改动带来的效益就是:只需要O(nW)的复杂的就能解决这个问题。这种方法一般也被称作记忆化搜索。
接下来我们进一步优化代码:
仔细研究前面的算法利用到的这个记忆化数组我们可以发现的是:不妨将res踢出来,用dp[i][j]直接根据rec的定义表示从容量为i价值为j的一堆物品中取出的总价值的最大值。于是我们有以下的递推式:
如上所示,这不就是我们熟悉的递推公式吗?直接粘代码:
//记忆化数组dp
int dp[MAX_N +1][MAX_W+1];
void solve(){
for(int i=n-1;i>0;i--){
for(int j=0;j<w;j++){
if(j<w[i]){
dp[i][j]=dp[i+1][j];
}
else{
dp[i][j]=max(dp[i+1][j],dp[i+1][j-w[i]]+v[i]);
}
}
}
printf("%d\n",dp[0][W]);
}
网上有各式各样的代码,以上代码也只提供一个思路,在于学会如何利用DP解决问题,同时如何一步一步的优化你的代码,下面直接上实战:
实战演练
记f(k,w):当背包容量为w,现有k件物品可偷,所能偷到的最大价值
不适应的小伙伴可以把f(k,w)替换成算法分析里面熟悉的dp
递推公式如下:
代码如下:(和算法分析的最终优化代码一毛一样)
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
int f[5][9]={0};
int w[5]={0,2,3,4,5};
int v[5]={0,3,4,5,8};
int main(){
int i,j;
memset(f,0,sizeof(f));
for(int i=1;i<5;i++){
for(int j=1;j<9;j++){
if(w[i]>j){
f[i][j]=f[i-1][j];
}
else{
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);
}
}
}
for(int i=0;i<5;i++){
for(int j=0;j<9;j++){
printf("f[%d][%d]=%d\n",i,j,f[i][j]);
}
}
return 0;
}
结果预览:
完毕。