记录结果再利用的“动态规划”
记忆化搜索
书中以背包为例子,和树状dp 不同,是种一维dp.
如果不记忆化,复杂度为O(n^2)
只不过,这种方法的搜索深度是n,而且每一层的搜索都需要两次分支,最坏就需要O(2”)的时间,
当n比较大时就没办法解了。所以要怎么办才好呢?为了优化之前的算法,我们看一下针对样例
输入的情形下rec递归调用的情况。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dz4iuIwJ-1606893759569)(C:\Users\Gumption\AppData\Roaming\Typora\typora-user-images\1605673215226.png)]
如图所示,rec以(3,2)为参数调用了两次。如果参数相同,返回的结果也应该相同,于是第二次调
用时已经知道了结果却白白浪费了计算时间。让我们在这里把第一次计算时的结果记录下来,省
略掉第二次以后的重复计算试试看。
记忆化搜索后就仅需O(nW)的复杂度(注*:n是物品个数,W是背包状态)
讲解了记忆化搜索的代码*(见图一)和不利于记忆化搜索的代码*(见码二)
码一:
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() {
//用-1表示尚未计算过,初始化整个数组
memset (dp, -1, sizeof (dp)) ;
printf ("%d\n", rec (0, W) ) ;
}
码二:
穷竭搜索的写法
如果对记忆化搜索还不是很熟练的话,可能会把前面的搜索写成下面这样
// 目前选择的物品价值总和是sum,从第1个物品之后的物品中挑选重量总和小于j的物品
int rec(int i, int j, int sum) {
int res;
if (i ==n) {
//已经没有剩余物品了
res = sum;
} else if (j<w[i]){
//无法挑选这个物品
res = rec(i + 1, j, sum) ;
} else {
//挑选和不挑选的两种情况都尝试一下
res = max(rec(i + 1, j, sum), rec(i + 1, j - w[i], sum +v[i]));
return res;
在需要剪枝的情况下,可能会像这样把各种参数都写在函数上,但是在这种情况下会让记忆
化搜索难以实现,需要注意。
两者的区别就是前者是返回截止每层的最大值(每层皆返回)
数都写在函数上,但是在这种情况下会让记忆
化搜索难以实现,需要注意。
两者的区别就是前者是返回截止每层的最大值(每层皆返回)
后者是到最后才返回,这样的动态规划不利于每层都记录。