问题省略…
深度优先搜索 (实例:解救小哈)
思路:让小哼往右边走,直到走不通的时候再回到这里,再去尝试另一个方向。规定一个顺序,按顺时针方向来尝试(即按照右、下、左、上的顺序去尝试)。
先检查小哼是否已经到达小哈的位置,如果没有到达则找出下一步可以走的地方。为了解决这个问题,此处dfs()函数只需要维护3个参数,分别是x坐标、y坐标、以及当前已经走过的步数step。dfs函数定义如下:
void dfs(int x,int y,int step) {
return;
}
判断是否已经到达小哈位置,只需要判断当前的坐标和小哈的坐标是否相等即可,如果相等则表明已经到达小哈的位置,如下:
void dfs(int x,int y,int step) {
if(x==p && y==q) {
//更新最小值
if(step<minn)
minn=step;
return ;//注意此处返回很重要
}
return;
}
如果没有找到小哈位置,则找出下一步可以走的地方。因为有四个方向可以走,根据顺时针方向来尝试(即按照右下左上顺序尝试),定义一个next方向数组,如下:
int next[4][2]={
{0,1},//向右
{1,0},//向下
{0,-1},//向左
{-1,0}};//向上
};
通过这个方向数组,使用循环就很容易获得下一步的坐标。此处将下一步的横坐标用tx存储,左边用ty存储。
for(int k=0;k<=3;k++) {
tx=x+next[k][0];
ty=y+next[k][1];
}
接下来要对tx、ty进行一些判断。包括是否越界、是否为障碍物,以及这个点是否已经在路径中(避免重复访问一个点)。需要用book[tx][ty]来记录格子(tx,ty)是否已经在路径中。
如果这个点符合所有要求,就对这个点进行下一步的扩展,即dfs(tx,ty,step+1).注意,此处的step+1,因为一旦从这个点开始继续往下尝试,意味着你的步数已经增加1.
for(int k=0;k<=3;k++) {
tx=x+next[k][0];
ty=y+next[k][1];
if(tx<1 || tx>n || ty<1 ||ty >m)
continue;
if(a[tx][ty]==0 && book[tx][ty]==0) {
book[tx][ty]=1;
dfs(tx,ty,step+1);
book[tx][ty]=0;
}
}
完整代码:
#include<cstdio>
#include<iostream>
using namespace std;
int a[51][51],book[51][51];
int n,m,p,q,minn=99999999;
void dfs(int x,int y,int step) {
//定义一个用于表示走的方向的数组
int next[4][2]={
{0,1},//向右
{1,0},//向下
{0,-1},//向左
{-1,0}};//向上
//判断是否到达小哈位置
if(x==p && y==q) {
//更新最小值
if(step<minn)
minn=step;
return ;//注意此处返回很重要
}
int tx,ty;
for(int k=0;k<=3;k++) {//枚举4种走法,计算下一个点的坐标
tx=x+next[k][0];
ty=y+next[k][1];
if(tx<1 || tx>n || ty<1 ||ty >m)//判断是否越界
continue;
if(a[tx][ty]==0 && book[tx][ty]==0) {//判断该点是否为障碍物或已经在路径中
book[tx][ty]=1;
dfs(tx,ty,step+1);
book[tx][ty]=0;
}
}
return ;
}
int main() {
cin>>n>>m;//读入n行,m列
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];//读入迷宫
int startx,starty;
cin>>startx>>starty>>p>>q; //读入起点、终点坐标
//从起点开始搜索
book[startx][starty]=1; //标记起点已经在路径中,防止后面重复走
dfs(startx,starty,0);//第一个参数是起点横坐标,第二个参数是起点纵坐标,第三个参数是初始步数为0
cout<<minn;
getchar();getchar();
return 0;
}
测试数据:
5 4
0 0 1 0
0 0 0 0
0 0 1 0
0 1 0 0
0 0 0 1
1 1 4 3
广度优先搜索 (实例:解救小哈)
思路:通过一层一层拓展方法来找到小哈,拓展时每发现一个点就将这个点加入到队列中,直至走到小哈的位置(p,q)时为止。
用一个结构体来实现队列。
struct note {
int x;
int y;
int s;
};
struct note que[2501];
int head,tail;
int a[51][51]={0};//用来存储地图
int book[51][51]={0};//作用是记录哪些点已经队列中,防止一个点被重复拓展,全部初始化为0
//最开始需要进行初始化,即将队列设置为空
head=1;
tail=1;
que[tail].x=1;
que[tail].y=1;
que[tail].s=0;
tail++;
book[1][1]=1;
}
然后从(1,1)开始,先尝试往右走到达(1,2)。
tx=que[head].x;
ty=que[head].y+1;
需要判断(1,2)是否越界。
//判断是否越界
if(tx<1 || tx>n || ty<1 || ty>m)
continue;
再判断(1,2)是否为障碍物或者已经在路径中
//判断是否为障碍物 或者已经在路径中
if(a[tx][ty]==0 && book[tx][ty]==0) {
}
若满足上述两个条件,则将(1,2)入队,并标记该点已经走过。
book[tx][ty]=1; //把这个点标记为走过,注意宽搜每个点只入列一次,所以和深搜不同,不需要将book数组还原
//插入新的点到队列中
que[tail].x=tx;
que[tail].y=ty;
que[tail].s=que[head].s+1;
tail++;
接下来还有继续尝试其他方向走。此处规定一个顺序,即按照顺时针方向来尝试(也就是以右下左上顺序尝试)。发现从(1,1)还是可以到达(2,1),因此 也需要将(2,1)也加入队列,实现代码与刚才一样。
对(1,1)拓展完毕后,(1,1)对我们已经没有用,此时将(1,1)出队。出队操作,即:
head++;
接下来需要在新拓展出的(1,2)和(2,1)这两点的基础上继续向下探索。到目前为止已经拓展出从起点出发一步以内可以到达的所有点。因为还没有到达小哈的位置,所以继续。直至走到小哈位置,算法结束、
完整代码:
#include<cstdio>
#include<iostream>
using namespace std;
struct note {
int x;
int y;
int f;//父亲在队列中的编号,本体不要求输出路径,可以不需要f
int s;
};
int main() {
struct note que[2501];
int a[51][51]={0},book[51][51]={0};
//定义一个用于表示走的方向的数组
int next[4][2]={
{0,1},//向右
{1,0},//向下
{0,-1},//向左
{-1,0}};//向上
int k,n,m,tx,ty,flag;
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
int startx,starty,p,q;
cin>>startx>>starty>>p>>q;
//队列初始化
int head,tail;
head=1;
tail=1;
//往队列插入迷宫入口坐标
que[tail].x=startx;
que[tail].y=starty;
que[tail].f=0;
que[tail].s=0;
tail++;
book[startx][starty]=1;
flag=0;//用来标记是否到达目标点,0表示暂时没有到达,1表示到达
while(head<tail) { //当队列不空时候循环
for(int k=0;k<=3;k++) {
//计算下一点坐标
tx=que[head].x+next[k][0];
ty=que[head].y+next[k][1];
//判断是否越界
if(tx<1 || tx>n || ty<1 || ty>m)
continue;
//判断是否为障碍物 或者已经在路径中
if(a[tx][ty]==0 && book[tx][ty]==0) {
book[tx][ty]=1; //把这个点标记为走过,注意宽搜每个点只入列一次,所以和深搜不同,不需要将book数组还原
//插入新的点到队列中
que[tail].x=tx;
que[tail].y=ty;
que[tail].f=head;//因为这个点是从head拓展而来,所以它的父亲是head,本题目不需要求路径,因此本句可以省略
que[tail].s=que[head].s+1;
tail++;
}
if(tx==p &&ty==q) {
flag=1;
break;
}
}
if(flag==1)
break;
head++;//此地方不能忘记,当一个点拓展结束,head++才能对后面的点再进行拓展
}
//打印队列中末尾最后一个点(目标点)的步数
//注意tail是指向队列队尾(即最后一位)的下一个位置,所以这需要-1
printf("%d",que[tail-1].s);
getchar();getchar();
return 0;
}
节选自《啊哈,算法》这一本书,作者啊哈