动态规划(DP: Dynamic Programming) 是算法的设计方法之一。我们今天从背包问题出发。
背包问题:
有n个重量和价值分别为Wi,Vi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
输入
n = 4
(w,v) = {(2,3,(1,2),(3,4),(2,2))}
w = 5
输出
7
这是一个著名的问题,先用朴素方法,针对每个物品是否放入背包进行搜索看看。
int n , w;
int w[MAX_N], v[MAX_N];
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,每一层的搜索都需要两次分支,复杂度是2的n次幂。
我们发现很多有参数相同的调用。白白浪费了计算时间。采用记忆化的改进。
int dp[MAX_N+1][MAX_W+1];
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));
}
研究下这个记忆数组。记dp[i][j]为根据rec 定义从第i个物品开始挑选总重小于j时,总价值的最大值。于是有下面递推式
dp[n][j] = 0
dp[i][j]
1. dp[i+1][j]. (j<w[i])
2. max(dp[i+1][j],dp[i+1][i-w[i]+v[i]]) (其他)
如上所述,不用写递归函数,直接利用递推公式将各项值计算出来。简单的用二重循环解决这一问题。
int dp[MAX_N+1][MAX_W+1]; //dp 数组
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]);
}
以这种方式一步步按顺序求出解的方法被称为动态规划法。
引用
[1] 挑战程序设计竞赛(第2版)