dfs记忆化搜索,动态规划

动态规划概念:

给定一个问题,将其拆成一个个子问题,直到子问题可以直接解决。然后把子问题的答案保存起来,以减少重复计算。再根据子问题的答案反推,得出原问题解。

821

运行时间长的原因:

重复大量计算

以5个台阶为例:

 正确做法:记录台阶已有的方案,比如用mem[]数组记录

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=30;
int n;
string words[N];//存单词
int used[N];//记录每个单词的使用次数
int g[N][N];//f[i][j] 存第i个单词能否接到第j个单词后面,存储相同子串长度
int res=0;
int mem[N];
int dfs(int x)//x代表遍历到的单词
{

if(mem[x])return mem[x];//如果以计算好直接返回
int sum=0;
if(x==1)
sum=1;
else if(x==2)//用else if
sum=2;
else sum=dfs(x-1)+dfs(x-2);
mem[x]=sum;//没有计算好则需要更新
return sum;
}
int main()
{
scanf("%d",&n);
int res=dfs(n);
printf("%d\n",res);
return 0;
}

递推算法dp:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=30;
int n;
int res=0;
int mem[N];
int main()
{
scanf("%d",&n);
mem[1]=1;
mem[2]=2;
if(n==1||n==2)
printf("%d",mem[n]);
for(int i=3;i<=n;i++)
{
mem[i]=mem[i-1]+mem[i-2];
}
printf("%d\n",mem[n]);
return 0;
}

进一步优化空间:

int newf=0,tmp1=1,tmp=2;

for(int i=3;i<=n;i++)

{

newf=tmp1+tmp2;

tmp1=tmp2;

tmp2=newf;} 

 记忆化搜索=暴力dfs+记录答案

递推的公式=dfs向下递归的公式

递推数组的初始值=递归的边界

acw1049

暴力搜索,分析图:

思路:最后一家店x只有两个可能,被选或者不被选,因为要尽可能的累加数值,所以被选的情况是一直累加到x-2,不被选的情况是只累加到x-1便结束

用home[N]数组保存已经选择的店家。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=30;
int n,k;
int res=0;
int mem[N];
int dfs(int x)//当前遍历到哪个店家
{
if(x>n)return 0;//dfs(5),dfs(6)都等于0,最终是可以组合的n个数字相加取最大值
else
return max(dfs(x+1),dfs(x+2)+mem[x]);//没被选择则是下一个数字,被选中则是隔一个数字
}
int main()
{
scanf("%d",&k);
while(k--)//循环输入k组数据
{
scanf("%d",&n);
for(int i=1;i<=n;i++)//位置是从1开始
{
scanf("%d",&mem[i]);//输入n个数字
}
int sum=dfs(1);
printf("%d\n",sum);
}

return 0;
}

动态规划思路:存储已知方案

注意:方案数组每次遍历完成后需要初始化

拓展:memset()函数

void *memset(void *s, int ch,size_tn);

函数解释:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。

memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法[1]

memset()函数原型是extern void *memset(void *buffer, int c, int count) buffer:为指针或是数组,c:是赋给buffer的值,count:是buffer的长度。

此处可以采用memset(mem,0,sizeof mem);对数组清0

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=30;
int n,k;

int mem[N];
int s[N];//记录走到当前位置的财富总和
int dfs(int x)//当前遍历到哪个店家
{int sum=0;
if(s[x]) return s[x];
if(x>n) sum=0;
else
sum=max(dfs(x+1),dfs(x+2)+mem[x]);//没被选择则是下一个数字,被选中则是隔一个数字
//sum不是累加,而是更新,所以不需要清0
s[x]=sum;//记录数据,需要更新
return sum;
}
int main()
{
scanf("%d",&k);
while(k--)//循环输入k组数据
{
scanf("%d",&n);
for(int i=1;i<=n;i++)//位置是从1开始
{
scanf("%d",&mem[i]);//输入n个数字
}
memset(s,0,sizeof s);//更新数组
int res=dfs(1);
printf("%d\n",res);
}
return 0;
}

s[i]存储的是:从i店铺开始能够获取的最大价值 

要实现记忆化搜索,dfs的参数就要尽可能的少,不应该把没有影响边界的参数放进来。

递推算法dp:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=30;
int n,k;
int F1[N];
int mem[N];
int s[N];//记录走到当前位置的财富总和
int main()
{
scanf("%d",&k);
while(k--)//循环输入k组数据
{
scanf("%d",&n);
for(int i=1;i<=n;i++)//位置是从1开始
{
scanf("%d",&mem[i]);//输入n个数字
}
memset(s,0,sizeof s);//更新数组
for(int i=n;i>=1;i--)//递推是由下往上,从f[4]=0开始递推
{
if(i>n)
F1[i]=0;
else
F1[i]=max(F1[i+1],F1[i+2]+mem[i]);
}
printf("%d\n",F1[1]);
}
return 0;
}

 做法是1~n探查,递推返回则从n回到1

想用1-n做递推,则需要从n开始枚举

F1[i]=max(F1[i-1],F1[i-2]+mem[i]);//当i为1,2时数组下标出现负数,下标同时加2得:

F1[i+2]=max(F1[i+1,F1[i]+mem[i]);

空间优化:

for(int i=1;i<n=;i++)

{newf=max(tmp1,tmp2+mem[i]);

tmp2=tmp1;

tmp1=newf;

}

  • 使用两个变量tmp1tmp2来存储前一个位置的最大财富和当前位置不选择前一个位置时的最大财富。
  • 通过一个循环,计算到每个位置时的最大财富,并存储在newf中。
  • 打印出最后一个位置的最大财富。

P1216 

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int r;
int F1[N];
int g[N][N];

int getMax(int x,int y)
{
if(x>r||y>r)
return 0;
else
//求最优子问题 dfs(x)=max(dfs(x+1),dfs(x+2));
//求 子问题的和 dfs(x)=dfs(x+1)+dfs(x+2);
return max(getMax(x+1,y),getMax(x+1,y+1))+g[x][y];
//在二维数组中左下角和右下角分别是向下一个或者向下向右一个
}
int main()
{
scanf("%d",&r);

for(int i=1;i<=r;i++)
{for(int j=1;j<=i;j++)
{
scanf("%d",&g[i][j]);}}
int max=getMax(1,1);
printf("%d\n",max);
return 0;
}

结果超时

树状图每种情况遍历,有重复计算的情况,例如第三层的 2 7 4 4 都可能被重复计算

优化:记忆化搜索

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int r;
int F1[N];
int g[N][N];
int mem[N][N];
int getMax(int x,int y)
{
if(mem[x][y]) return mem[x][y];
int sum=0;
if(x>r||y>r)
sum=0;
else
//求最优子问题 dfs(x)=max(dfs(x+1),dfs(x+2));
//求 子问题的和 dfs(x)=dfs(x+1)+dfs(x+2);
sum=max(getMax(x+1,y),getMax(x+1,y+1))+g[x][y];
mem[x][y]=sum;
return sum;
//在二维数组中左下角和右下角分别是向下一个或者向下向右一个
}
int main()
{
scanf("%d",&r);

for(int i=1;i<=r;i++)
{for(int j=1;j<=i;j++)
{
scanf("%d",&g[i][j]);}}
int max=getMax(1,1);
printf("%d\n",max);
return 0;
}

递推思路:dfs采用的是从上到下推,那么递推就是从下到上,最后返回f[1][1]

int f[N][N];
int main()
{
scanf("%d",&r);

for(int i=1;i<=r;i++)
{for(int j=1;j<=i;j++)
{
scanf("%d",&g[i][j]);}}
for(int i=r;i>=1;i--)
{
for(int j=1;j<=r;j++)
{f[i][j]=max(f[i+1][j],f[i+1][j+1])+g[i][j];
//例:r=5时,f[5][1]=max(f[6][1],f[6][2])+g[5][1]
//f[6][1],f[6][2]没有赋值,都为0,f[5][1]=g[5][1],同理f[5][2]=g[5][2]
//f[4][1]=max(f[5][1],f[5][2])+g[4][1];那么f[4][1]=5+2=7
}}
printf("%d\n",f[1][1]);
return 0;
}

思考:从从上往下推

int f[N][N];
int main()
{
scanf("%d",&r);

for(int i=1;i<=r;i++)
{for(int j=1;j<=i;j++)
{
scanf("%d",&g[i][j]);}}
for(int i=1;i<=r;i++)
{
for(int j=1;j<=i;j++)
{f[i][j]=max(f[i-1][j],f[i-1][j-1])+g[i][j];
}}
//注意最底层五个数字均可能作为最后一个数字,因此应该对比选择一个最大值
int res=0;
for(int i=1;i<=r;i++)
{
res=max(f[r][i],res);
}
printf("%d\n",res);
return 0;
}

背包问题 

思考:

1,每个物品都有两个可能,选和不选

2,被选中的物品的体积不可以大于剩余的背包的容量

 记忆化搜索将时间复杂度从2^n降到n*v

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;//注意是静态变量
int n,V;
int mem[N][N];
//从第x个物品开始(x~n),总体积小于等于V的最大价值
//利用位置和剩余体积记录已经有结果的分支
int v[N];//体积数组
int w[N];//价值数组
int dfs(int x,int spv)//x表示遍历到的物品,spv表示剩余的体积
{int sum=0;
if(mem[x][spv]) return mem[x][spv];//已有结果剪枝
if(x>n) sum=0;//结束标志
else if(v[x]>spv)//遍历下一个
sum=dfs(x+1,spv);
else if(v[x]<=spv)
sum=max(dfs(x+1,spv),dfs(x+1,spv-v[x])+w[x]);
//有两种可能:选和不选,需要对比选择最大值
mem[x][spv]=sum;
return sum;
}
int main()
{
scanf("%d %d",&n,&V);
for(int i=1;i<=n;i++)//注意下标是从1开始
{scanf("%d %d",&v[i],&w[i]);
}
int res=dfs(1,V);
printf("%d\n",res);
}

易错点:

1,注意N静态变量

2,注意数字下标是从1开始遍历

dp递推:用状态数组代替dfs函数f[N][N]两个维度分分别是遍历到的物品和背包剩余体积

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;//注意是静态变量
int n,V;
int f[N][N];
int v[N];//体积数组
int w[N];//价值数组
int main()
{
scanf("%d %d",&n,&V);
for(int i=1;i<=n;i++)//注意下标是从1开始
{scanf("%d %d",&v[i],&w[i]);
}
for(int i=n;i>0;i--)
{
for(int j=0;j<=V;j++)//背包剩余体积的范围是0-m
{
if(v[i]>j)
f[i][j]=f[i+1][j];
if(v[i]<=j)
f[i][j]=max(f[i+1][j],f[i+1][j-v[i]]+w[i]);
}
}
printf("%d\n",f[1][V]);//背包装满时
}

从上往下推:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;//注意是静态变量
int n,V;
int f[N][N];
int v[N];//体积数组
int w[N];//价值数组
int main()
{
scanf("%d %d",&n,&V);
for(int i=1;i<=n;i++)//注意下标是从1开始
{scanf("%d %d",&v[i],&w[i]);
}
for(int i=1;i<=n;i--)
{
for(int j=0;j<=V;j++)//背包剩余体积的范围是0-m
{
if(v[i]>j)
f[i][j]=f[i-1][j];
if(v[i]<=j)
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
}
}
printf("%d\n",f[n][V]);//背包装满时
}

空间优化:

for(int i=1;i<=n;i++)

{

for(int j=0;j<=V;j++)

{

g[j]=f[j];

if(j>=v[i])

{

g[j]=max(f[j],f[j-v[i]]+w[i]);

}}

memcpy(f,g,sizeof f);

}

printf("%d\n",f[m]);

拓展:只使用滚动数组f[n]解决问题

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值