深搜与广搜是搜索算法中最常用的两种算法,通过深度优先搜索解决问题还会用到回溯和剪枝,让我们一起进入本章,了解深搜的基本概念和模板,并学会解决一些常见问题。
目录
问题导入
走迷宫问题
现在有一个3*3的迷宫,小知在迷宫的左上角,迷宫出口在右下角,请你帮小知算一算,他有多少种方案可以走出迷宫(每个格子不能重复走动)。
迷宫中显示0的点,是不可以走的。小知每次只能到达相邻的上下左右4个格子。
如何走?
1.如果当前出发的格子是终点,则程序结束。
2.每次可以选择的格子有:上(A)、下(B)、左(C)、右(D)。
3.按照顺序尝试每个格子是否可走。
4.找到第一个可以走的格子(假设为B),标记该格子,表示已经走过。
5.再从格子(B)出发,重复上述过程(递归进行)。
6.将格子(B)消除标记,再尝试从格子(C、D)出发去找路线。
问题建模
如何表示迷宫地图等信息呢?
使用一个3*3的二维数组maze[][]来存储迷宫信息,如果值位0表示不可走,1表示可走。
使用一个3*3的二维数组used[][]来标记是否走过,没走过为0,走过的话为1。
例:used[1][2]=1,表示maze[1][2]已经走过。
如何表示每次移动的过程?
每次移动,实际上就是坐标的变化。
上:行标-1,列标不变。
下:行标+1,列标不变。
左:行标不变,列标-1。
右:行标不变,列标+1。
我们可以用一个二维数组表示移动的方向。
例:当前坐标为(1,2),向上移动就是:(1+fx[0][0],2+fx[0][1]),得到(0,2)。
走迷宫问题参考程序
int ans=0,maze[6][6],used[6][6],fx[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
void dfs(int x,int y){
if(x==3&&y==3){ //如果当前找的点就是终点,则方案数+1,结束本次搜索。
ans++; return ;
}
else{
used[x][y]=1; //将当前所在的点标记为1,表示走过
for(int i=0;i<4;i++) { //尝试上、下、左、右4个方向
int nx=x+fx[i][0];int ny=y+fx[i][1]; //下一次查找的点的坐标nx,ny
//nx,ny要在地图内,并且可走,并且未被走过。
if(nx>=1&&nx<=3&&ny>=1&&ny<=3&&used[nx][ny]==0&&maze[nx][ny]==1){
used[nx][ny]=1; //表示nx,ny表示走过
dfs(nx,ny); //从nx,ny再去找
used[nx][ny]=0; //消除标记,再尝试其他方向。
}
}
used[x][y]=0; //消除标记
}
}
int main(){
for(int i=1;i<=3;i++){
for(int j=1;j<=3;j++){
cin>>maze[i][j];
}
}
dfs(1,1);
cout<<ans;
return 0;
}
深度优先搜索概述
我们走迷宫的过程就是一个深度优先搜索的过程:从可以解决问题的某一个方向出发,并一直深入寻找,找到这个方向可以得到的所有解决方案,如果找不到,则回退到上一步,从另一个方向开始,再次深入寻找。
解决问题时的注意事项
1.首先弄清楚问题的解空间,即迷宫有多大。
2.弄清楚搜索的边界,即到哪一步就该停下,不用再搜。
3.搜索的方向,即可能包含哪几种子问题。
void dfs()//参数用来表示状态
{
if(到达终点状态){
...//根据题意添加
return;
}
if(越界或者是不合法状态)
return;
if(特殊状态)//剪枝
return ;
for(所有可能的下一状态){
if(状态符合条件){
修改操作;//根据题意来添加
标记;
dfs();
(还原标记);
//是否还原标记根据题意
//如果加上(还原标记)就是 回溯法
}
}
}
训练:迷宫
给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过。给定起点坐标和终点坐标,问: 每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案。在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。
【输入描述】第一行N、M和T,N为行,M为列,T为障碍总数。第二行起点坐标SX,SY,终点坐标FX,FY。接下来T行,每行为障碍点的坐标。
【输出描述】给定起点坐标和终点坐标,问每个方格最多经过1次,从起点坐标到终点坐标的方案总数。
【输入样例】2 2 1
1 1 2 2
1 2
【输出样例】1
参考代码
#include<bits/stdc++.h>
using namespace std;
int ans=0,maze[6][6],used[6][6];
int Fx[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
int n,m,t,sx,sy,fx,fy,tx,ty;
void dfs(int x,int y,int s,int e){
if(x==s&&y==e){
ans++; return ;
}
else{
used[x][y]=1;
for(int i=0;i<4;i++) {
int nx=x+Fx[i][0];int ny=y+Fx[i][1];
if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&used[nx][ny]==0&&maze[nx][ny]==0){
used[nx][ny]=1;
dfs(nx,ny,s,e);
used[nx][ny]=0;
}
}
used[x][y]=0;
}
}
int main(){
cin>>n>>m>>t;
cin>>sx>>sy>>fx>>fy;
for(int i=1;i<=t;i++){
cin>>tx>>ty;
maze[tx][ty]=1;
}
dfs(sx,sy,fx,fy);
cout<<ans;
return 0;
}
训练:全排列问题
输入整数n(n<=9),输出1~n的所有排列方式。
例:n=3,全排列为123,132,213,231,312,321。
【输入描述】输入一个正整数n
【输出描述】输出1~n之间的全排列(n<=9),换行输出
【输入样例】3
【输出样例】123
132
213
231
321
312
参考代码
int ans[10],used[10];
void dfs(int x,int n){
if(x>n){
for(int i=1;i<=n;i++)
cout<<ans[i];
cout<<endl;
return;
}
for(int i=1;i<=n;i++){
if(!used[i]){
ans[x]=i;
used[i]=1;
dfs(x+1,n);
used[i]=0;
}
}
}
int main(){
int n;
cin>>n;
dfs(1,n);
return 0;
}