【基本算法】深度优先搜索DFS与广度优先搜索BFS
深搜(depth first search)和广搜(breadth first search)是两种基本搜索算法,均采用穷举策略
下面以老鼠走迷宫(maze.cpp)为例给出它们的模板
题目
【问题描述】
一只老鼠从迷宫的左上角走到右下角(如下图),中间不能穿越障碍(阴影部分)
任务:给出迷宫的形状,请你求出老鼠经过的最少格子数量。
【输入格式】
第一行,为两个整数n和m(1≤n,m≤20),表示迷宫总共有几行几列;
从第二行开始到第n+1行,每行m列为迷宫形状,“0”表示无障碍,“1”表示有障碍,
相邻连个数中间用一个空格隔开。
【输出格式】
输出一个整数,表示经过的最短路径长度(每个格子长度为1);
如果没有一条可行的路,则输出“-1” 。
【输入样例】maze.in
6 9
0 0 0 1 1 0 0 0 1
0 1 0 0 0 0 1 0 1
0 1 0 1 0 1 1 0 1
0 1 0 1 0 0 0 0 0
0 1 1 0 1 1 0 1 0
0 0 0 0 0 0 0 1 0
【输出样例】maze.out
14
深度优先搜索DFS
深搜的思想是从一个起点开始,单向向前进,遇到不合法的情况就回溯。
- 深搜需要运用到递推与回溯
- (回溯法是思想,深搜是算法,策略!)
- 基本模型
void DFS( 状态i )
{
操作;
跳到状态i+1;
if( 状态i+1合法 )
DFS( 状态i+1 );
操作;
}
按这个模型,得到maze.cpp的深搜法代码:
//DFS
#include<bits/stdc++.h>
using namespace std;
int n,m,x,y;
bool Map[21][21],Way[21][21];//Map记录障碍,Way记录是否走过
const int fx[4]={-1,0,1,0};//老鼠向四个方向移动,点的变化量(上,右,下,左)
const int fy[4]={0,1,0,-1};
int len=0x7f7f7f;//len是进过距离的最小值
void DFS(int step,int a,int b) //准备进行第step步,当前位置a,b
{
int i,x,y; //attention!!y1是函数!!会歧义!!!!
for(i=0;i<4;i++){ //枚举四个方向
//新位置
x=a+fx[i];
y=b+fy[i];
//判断新位置是否合法,不合法就不能走,跳到下一个for循环
if(x>0&&x<=n&&y>0&&y<=m&&!Map[x][y]&&!Way[x][y]){
Way[x][y]=1;//做走过的标记
if(x==n&&y==m)len=min(len,step);//到终点了,记录最小步数
DFS(step+1,x,y);//准备执行第step+1步,当前位置x,y
Way[x][y]=0;//取消走过的标记:其他走法可能还要用
}
}
}
int main(){
freopen("01 maze.in","r",stdin);
freopen("01 maze.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&Map[i][j]);
Way[1][1]=1;//出发位置标记
DFS(2,1,1);//开始
if(len==0x7f7f7f)puts("-1");
else printf("%d",len);
return 0;
}
当然,既然我们只求最短路径长度,可以对深搜过程进行优化:
//判断新位置是否合法,不合法就不能走,跳到下一个for循环
if(x>0&&x<=n&&y>0&&y<=m&&!Map[x][y]&&!Way[x][y]){
Way[x][y]=1;//做走过的标记
if(x==n&&y==m)len=min(len,step);//到终点了,记录最小步数
else
{
if(step+1<len)//优化:如果步数已经超过目前的最小步数了,就不再执行
DFS(step+1,x,y);//准备执行第step+1步,当前位置x,y
}
Way[x][y]=0;//取消走过的标记:其他走法可能还要用
}
广度优先搜索BFS
广搜的思想是从起点开始搜索每一个点,它与深搜的不同在于它同时考虑所有的情况(所以叫广度)
- 利用队列实现广搜
- 基本模型
void BFS(){
while(存在下一种状态OR队列不空){
得到下一种状态;//如果每种状态存在多组数据,可以用结构体存储
操作;
此状态入队;
if(状态合法)操作;
}
}
队列:
1 | 2 | 3(1得到) | 4(1得到) | 5(2得到) | 6(2得到) | 7(3得到) | …… |
---|---|---|---|---|---|---|---|
状态1 | 状态2 | 状态3 | 状态4 | 状态5 | 状态6 | 状态7 | …… |
按这个模型,得到maze.cpp的广搜法代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,head,tail;//head,tail分别为队列的头指针与尾指针--也就是下标
bool Map[105][105],Way[105][105];
const int fx[4]={-1,0,1,0};
const int fy[4]={0,1,0,-1};
int x,y,len;
struct st
{
int x;
int y;
int len;
};
st loc[10005];//定义为队列数组--队列只是模型
void BFS(){
while(head<tail)
{
head++;
for(int i=0;i<4;i++)
{
int a=loc[head].x+fx[i];
int b=loc[head].y+fy[i];
if(a>0&&a<=n&&b>0&&b<=m&&!Way[a][b]&&!Map[a][b])//判断合法
{
tail++;
loc[tail].x=a;
loc[tail].y=b;
loc[tail].len=loc[head].len+1;
Way[a][b]=1;
if(a==n&&b==m)
{
printf("%d",loc[tail].len);
return;//能达到最小值就结束了
}
}
}
}
puts("-1");//程序自然结束,没有到过终点
return;
}
int main()
{
freopen("01 maze.in","r",stdin);
freopen("01 maze.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&Map[i][j]);
//初始化
memset(loc,0,sizeof(loc));
head=0;
tail=1;
Way[1][1]=1;
loc[tail].x=1;
loc[tail].y=1;
loc[tail].len=1;
BFS();
return 0;
}