写在前面
我们常使用的不带返回值的dfs,void dfs(…),一般是从起点搜索到终点,就像走迷宫一样,从本状态列出下一步可走的状态,然后dfs下一步可走的状态,一直达到终点。这类dfs属于递推(从小(边界条件)往大搜索),比如二叉树的先根序序遍历(void preorder),先遍历根结点,再往左子树,右子树递推着遍历。
但是有时候,我们发现将大问题分解为小问题直至问题边界更利于代码的书写,这类就属于递归,比如求斐波那契数列,f[n]=f[n-1]+f[n-2],求f[n]的问题就被转换为求f[n-1]和f[n-2],这里可以使用dp或者带返回值的dfs。
dp代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=10;
int dp[maxn];
int main()
{
dp[1]=1;
dp[2]=1;
for(int i=3;i<maxn;i++)
{
dp[i]=dp[i-1]+dp[i-2];
}
cout<<dp[9];//求数列第9位
return 0;
}
带返回值的dfs代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=10;
int dp[maxn];
int dfs(int n)//求数列第n位的值,相当于求dp[9]
{
if(n==1||n==2) return 1;
else return dfs(n-1)+dfs(n-2);
}
int main()
{
cout<<dfs(9);
return 0;
}
可以发现,dfs(n-1)和dfs(n-2)的递归是有重叠部分的,这里通常可以使用记忆化dfs进行优化。
这样既拥有了dp的效率,也拥有了带返回值dfs的简洁。
#include<bits/stdc++.h>
using namespace std;
const int maxn=10;
int dp[maxn];
int f[maxn];
int dfs(int n)//求数列第n位的值,相当于求dp[9]
{
if(f[n]) return f[n];只要f[n]计算过了,就保存起来,每个值最多算一次,避免了重复计算
f[n]=dfs(n-1)+dfs(n-2);
return f[n];
}
int main()
{
f[1]=1;
f[2]=1;
cout<<dfs(9);
return 0;
}
递归dfs(记忆化dfs)
例题
1.作物杂交
递推dfs
模板1:
理解全排列代码即可:
#include<iostream>
const int maxn=11;
int n,P[maxn];
bool hashTable[maxn]={false};
//目的:排列1-n
//数组P用于存储排列结果,下标为0-(n-1)
void GenerateP(int index)
{
//第一步结束条件
if(index==n)//递归为index+1;因为n-1还要执行;结束条件为n-1+1=n
{
for(int i=0;i<n;i++)
{
printf("%d",P[i]);//输出结果
}
printf("\n");
return;
}
//要排列1-n,在一个固定的index下遍历1-n
for(int x=1;x<=n;x++)
{
//代码段1
if(hashTable[x]==true) continue;
//
P[index]=x;
hashTable[x]=true;
GenerateP(index+1);//在hashTable[x]=true的情况下向后遍历,写到这一段后再写代码段1
hashTable[x]=false;
}
}
int main()
{
n=4;
GenerateP(0);
system("pause");
return 0;
}
模板2:
void dfs(现有状态)
vis[现有状态]=true;
if(达到目标状态)
输出;
千万注意:这里不加return
因为如果加return,达到目标状态后就不能回溯
不加return还有原因:目标状态是没有下一状态的,
也就是说达到目标状态后,下面的for循环都会被continue掉;
for循环
写出下一状态;
如果状态不符合则continue;
dfs(下一状态);
vis[现有状态]=false;//回溯