记忆化搜索实际上是用递归来实现的,但是递归的过程中有许多的结果是被反复计算的,这样会大大降低算法的执行效率。
而记忆化搜索就是在递归的过程中,将已经计算出来的结果有数组保存起来,当之后的计算用到的时候直接取出结果,避免重复运算,因此极大的提高了算法的效率。
接下来看两个例题帮助理解:
例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
来源:知乎