记忆化搜索

记忆化搜索实际上是用递归来实现的,但是递归的过程中有许多的结果是被反复计算的,这样会大大降低算法的执行效率。
  而记忆化搜索就是在递归的过程中,将已经计算出来的结果有数组保存起来,当之后的计算用到的时候直接取出结果,避免重复运算,因此极大的提高了算法的效率。
  接下来看两个例题帮助理解:
 例1: https://www.luogu.com.cn/problem/P1464
  本题提醒了当a,b,c均为15时,调用的次数将非常多,所以我们可以想到另开一个数组来记录已经计算出来的结果,之后再次遇到同样的a,b,c就可以直接返回结果。
  AC代码:

#include<iostream>
#include<cstdio>
using namespace std;
long long a,b,c;
long long vis[25][25][25];
long long dfs(int a,int b,int c){
 if(a<=0||b<=0||c<=0)return 1;
 else if(a>20||b>20||c>20)return dfs(20,20,20);
 else if(vis[a][b][c])return vis[a][b][c];//记忆化搜索
 else if(a<b&&b<c)vis[a][b][c]=dfs(a,b,c-1)+dfs(a,b-1,c-1)-dfs(a,b-1,c);
 vis[a][b][c]=dfs(a-1,b,c)+dfs(a-1,b-1,c)+dfs(a-1,b,c-1)-dfs(a-1,b-1,c-1);
 return vis[a][b][c];
}
int main(){
 while(scanf("%lld%lld%lld",&a,&b,&c)){
  if(a==-1&&b==-1&&c==-1){
    break;
  }
  for(int i=0;i<=20;i++){
   for(int j=0;j<=20;j++){
    for(int k=0;k<=20;k++){
     vis[i][j][k]=0;
    }
   }
  }
  long long ans=dfs(a,b,c);
  printf("w(%lld, %lld, %lld) = %lld\n",a,b,c,ans);
 }
 return 0;
}

例2:https://www.luogu.com.cn/problem/P1434
本题中在我们进行dfs便利的时候可能会遇到我们之前已经搜过的点,此时就没有必要再去搜一遍了,我们可以开一个数组来记录各个坐标上的结果。
AC代码:

#include<iostream>
#include<cstdio>
using namespace std;
int r,c;
int map[105][105];
int vis[105][105];
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
int dfs(int x,int y){
 if(vis[x][y]!=1)return vis[x][y];//如果x,y已经搜过,直接返回结果。
 for(int i=0;i<4;i++){
  int tx=x+dx[i];
  int ty=y+dy[i];
  if(tx>=1&&tx<=r&&ty>=1&&ty<=c&&map[tx][ty]<map[x][y]){
   vis[x][y]=max(vis[x][y],dfs(tx,ty)+1);//这个应该很好理解,四个方向取最大的值。
  }
 }
 return vis[x][y];
}
int main(){
 cin>>r>>c;
 for(int i=1;i<=r;i++){
  for(int j=1;j<=c;j++){
   cin>>map[i][j];
   vis[i][j]=1;
  }
 }
 int ans=0;
 for(int i=1;i<=r;i++){
  for(int j=1;j<=c;j++){
   if(vis[i][j]==1)ans=max(ans,dfs(i,j));//vis[i][j]=1说明坐标从未被搜过。
  }
 }
 cout<<ans<<endl;
 return 0;
}

另外,记忆化搜索和动态规划(dp)有着很深的联系。记忆化搜索是动态规划(DP思想)的一种实现形式。dp的思想就是根据状态转移方程递推求解。
在某些特定的情况下,递推和记忆化搜索并不能相互转化。题2就是一个典型的例子。
我们假设f[i][j]表示滑到坐标(i,j)所能滑到的最长长度。那么对于状态f[i][j]而言,它可以由f[i-1][j],f[i][j-1],f[i+1][j],f[i][j+1]四个状态推得,然而我们使用普通的递推(两个for)只能得到上、左两个方向的状态,右、下两个方向的状态却无从得知,因此使用递推就不能满足我们的要求,如果再补上两个for覆盖右、下状态,那么时间复杂度就变为了N4,很明显会TLE。这题如果采用记忆化搜索,在搜索的过程中发现f[i][j]在以前的某个时刻已经被计算过,直接return,可以保证时间复杂度为N2

因此,我认为记忆化搜索通常是用在递推关系不那么明确的情况下,使用记忆化可以避免考虑复杂的DP顺序。当然,线性的递推也可以使用记忆化搜索实现,但是会带来不必要的系统栈占用,导致程序性能下降。
另外:在特定问题下,题目对于DP的效率有着较高的要求,时间要求比如单调队列优化DP、斜率优化DP或者有空间要求(滚动数组优化),在这种情况下,我们就不得不递推求解。比较经典的一个问题是:01背包+打印方案我们都知道01背包的经典方程是:f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]) , j>=w[i]使用记忆化搜索可以实现这个二维的递推过程,但是如果题目对空间有要求,要求你优化掉物品数量一维(优化这一维在递推实现中是很容易的),你就会发现记忆化搜索就出现了很大的问题,因为对于同一剩余背包空间容量,可能可以由多个i推得,我们无法控制dp顺序,而01背包的递推优化正是建立在巧妙地变化枚举顺序的基础上进行的,因此记忆化搜索也就无能为力了。
参考文献:
链接:https://www.zhihu.com/question/60730045/answer/462829337
来源:知乎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值