深度优先搜索
dfs函数:
1.明确dfs函数的参数是什么意思
2.设置递归边界,什么时候输出一种答案
3.某一层的dfs没有到达递归边界怎么进行循环来尝试这一层的所有可能操作
4.进行完一个分支之后回溯把book设置为0,在全排列问题中是为了防止不设置为0的话根本无法获得n个数。
全排列问题
//****深度优先搜索****
//全排列问题
#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int book[N],n,a[N];
void dfs(int num)//num不是表示数据是表示已经有几个数了
{
if(num>n)//符合递归边界就输出
{
for(int i=1;i<=n;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
//这样写的话就会一直输出一个特定的结果了
//所以需要设置一个数组存结果
// for(int i=1;i<=n;i++)
// {
// if(book[i])
// {
// cout<<i<<" ";
// }
// }
}
else//还可以继续探索就先把当前点设置为已访问
{
for(int i=1;i<=n;i++)
{
if(!book[i])
{
a[num]=i;
book[i]=1;
dfs(num+1);
book[i]=0;
}
}
}
}
int main()
{
cin>>n;
dfs(1);
return 0;
}
组合问题:主体与排列问题差不多,用数组存数,到边界就输出,
但是在递归过程中,有一个要求因为1,2和2,1这个排列相同。
所以组合问题与排列问题最大的区别是,在一层dfs中的for循环中,全排列只要找n个数里面还没有被找到的那个点即可,但是组合问题要求要从上一个数的后面去找,这样就不会出现1,2和2,1了
所以dfs里面加上一个参数来表示:前面的组合数的后一个树,应该从这开始向后找
//****深度优先搜索****
//组合问题
#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int book[N],n,a[N],m;
void dfs(int num,int num2)//num不是表示数据是表示已经有几个数了
{
if(num>m)//符合递归边界就输出
{
for(int i=1;i<=m;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
else//还可以继续探索就先把当前点设置为已访问
{
for(int i=num2;i<=n;i++)
{
if(!book[i])
{
a[num]=i;
book[i]=1;
dfs(num+1,i+1);
book[i]=0;
}
}
}
}
int main()
{
cin>>n>>m;
dfs(1,1);
return 0;
}
发现深度优先搜索比动态规划的计算的思路过程复杂,但是 深度优先搜索做题很好做。
对于N皇后问题,不能在同行,同列,同一条主副对角线,都是同样的模板,
dfs函数的参数就是已经运行到第几行了
递归边界就是num>n
递归中对dfs这行的所有可能的列进行测试,符合条件就直接标记好,然后继续去看下一行,
//****深度优先搜索****
//N皇后问题
#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int book[N],n,a[N];
int zhudui[N],fudui[N];
int sum;
void dfs(int num)
{
if(num>n)//到第几行了
{
sum++;
}
else
{
for(int i=1;i<=n;i++)//第num行的第i列
{
if(!book[i]&&!zhudui[num-i+n]&&!fudui[num+i])
{//找到一个符合条件的点事这行这列这两条对角线上的第一个点
book[i]=1;//这一列有了
zhudui[num-i+n]=fudui[num+i]=1;
dfs(num+1);//在这一列有的情况之下继续进行下一层
zhudui[num-i+n]=fudui[num+i]=book[i]=0;//回溯
}
}
}
}
int main()
{
cin>>n;
dfs(1);
cout<<sum<<endl;
return 0;
}
迷宫问题
前面的组合问题和排列问题的一个区别就是下一层的dfs函数里面的循环必须在上一层的下一个才可以,所以在dfs里面添加了一个参数:表示上一层选择的数的下一个
在迷宫问题里面,还是在dfs里面每次循环基于的是上一个点的左右上下,所以在dfs里面除了num这个表示步数的,还有xy表示dfs里面遍历的起点,也就是上一层里面找的或者左右或者上下的一个点,在这一层里面再去考虑符合条件的点(这里很容易可以看出在下一层dfs之前为什么要让book==1了);
//****深度优先搜索****
//迷宫问题求解
//很容易想到dfs里面的遍历是对当前点的周围所有点的尝试
#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int book[N][N];
int minn=100000,sx,sy,m,n;
char s[N][N];
int f[4][2]={-1,0,1,0,0,1,0,-1};
int in(int xpos,int ypos)
{
if(xpos<=m&&ypos<=n&&xpos>=0&&ypos>=0)
return 1;
return 0;
}
void dfs(int x,int y,int num)//走了几步
{
if(s[x][y]=='T')
{
minn=min(minn,num);
}
else
{
int xpos,ypos;
for(int i=0;i<4;i++)
{
xpos=x+f[i][0];
ypos=y+f[i][1];
if(in(xpos,ypos)&&!book[xpos][ypos]&&s[xpos][ypos]!='#')
{
book[xpos][ypos]=1;
dfs(xpos,ypos,num+1);
book[xpos][ypos]=0;
}
}
}
}
int main()
{
cin>>m>>n;
//说明了几行几列就两层for输入
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
cin>>s[i][j];
if(s[i][j]=='S')//记录起点在哪里
{
sx=i;
sy=j;
}
}
}
dfs(sx,sy,0);
cout<<minn;
}
不同问题的目的不一样:
N皇后问题:只有一个参数表示行数
全局上来看就是在图的所有行都放一个皇后;
递归边界就是最后一行放完了;
遍历的顺序是这一行的所有的点是否符合;
迷宫问题:两个参数即为xy坐标
全局上来看是为了找到一条从起点到终点的最短路径,或者根本没有路径;
递归边界就是找到了终点;
遍历的顺序是考察所有的前后左右的点;
棋盘问题:因为递归的边界是所有的棋子都放完了,必然有一个参数存棋子数,因为要不同行,不同列,所以为了维护这两个性质,再拿一个参数来存行数;
全局上来看是要把所有的棋子全部放到棋盘上;
递归边界是所有棋子都放完了;
棋盘问题要求是组合问题而不是排列问题,所以必须从当前行的后
一行开始遍历,
是一个两层的循环。
棋盘问题与组合问题类似,第一个参数都是第几个,第二个参数都是有几个了
但是在遍历的时候,组合问题只是从第几个的后面开始遍历,只要没有标记就放进结果数组
棋盘问题遍历的时候也是从之前行的下一行开始遍历,但是选择很多还要遍历当前行的所有列是否符合要求。所以是两层循环。
//****深度优先搜索****
//棋盘问题
#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int book[N];
int sum,m,n;
char s[N][N];
void dfs(int num,int num2)//到了第几行了,还剩num个
{
cout<<"目前是第"<<num<<endl;
if(num2==0)
{
sum++;
}
else
{
for(int j=num; j<=n; j++)
{//因为要求是组合问题,必须从上一层的下一个开始遍历
for(int i=1; i<=n; i++) //遍历所有列
{
if(!book[i]&&s[j][i]=='.')
{
book[i]=1;
dfs(num+1,num2-1);
book[i]=0;
}
}
}
}
}
int main()
{
cin>>n>>m;
//说明了几行几列就两层for输入
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
cin>>s[i][j];
}
}
dfs(1,m);
cout<<sum;
}