迷宫问题很经典,对于学习搜索很有帮助,特别是针对深搜,下面我们就来看关于迷宫中的一些常见的问题:
- 从起点到终点的最小步数(前提是起点和终点是可达的)
- 打印出起点到终点的所有通路。
- 打印起点到终点的一条最短路径
1.先来看第一个问题:从起点到终点的最小步数,迷宫中可能起点到终点存在很多的通路(我们找最小的步数,需要把所有的通路都搜遍,才能确定),而我们只用找最小的那条通路的步数就可以了(我们可以想象下,现在给你K个数,叫你去找最小的那个数,你是怎么找的,相信学过编程的你一定不会陌生),为简单起见,我预设的迷宫的起点为左上方,终点为右下方,下面附上具体代码:
#include<iostream>
using namespace std;
//迷宫的维数
#define MAXN 5
int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};
int maze[MAXN][MAXN];
int step_min = 10000000;
void dfs(int depth,int x,int y)
{
//0代表可行,1代表不可行
if(x>=0&&x<MAXN&&y>=0&&y<MAXN&&maze[x][y]==0)
{
if(x==y&&y==MAXN-1)
{
step_min = step_min > depth ? depth : step_min ;
}
maze[x][y] = 1;
for(int i=0;i!=4;++i)
{
dfs(depth+1,x+dir[i][0],y+dir[i][1]);
}
maze[x][y] = 0;
}
}
int main()
{
for(int i=0;i!=MAXN;++i)
{
for(int j=0;j!=MAXN;++j)
{
cin >> maze[i][j] ;
}
}
dfs(0,0,0);
if(step_min==10000000)
{
cout << "不可行" << endl;
}
else
{
cout << "所需的最小步数是:" << step_min << endl;
}
}
对于搜索最短路径,广搜搜出来的就是一个最短路径,还不用向我们这样,要把所有的通路的都搜出来,做个比较,广搜为什么可以?因为广搜是同步推进的,正如你在河里面扔一个石头,肯定会激起一系列的同心圆,而这些同心圆一遇到岸边,那么就不会继续推进了(抽象的有点。。。。),下面给出用广搜搜索最短路径的代码:
#include<iostream>
#include<queue>
#include<utility>
using namespace std;
#define MAXN 5
#define INF -10000
//由于迷宫里面一个点关联到行和列,此处用了pair来存放数据
//save数组里面保存的是起点到搜索过的点的最小步数
typedef pair<int,int> PAIR;
int maze[MAXN][MAXN],save[MAXN][MAXN];
int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};
int bfs()
{
queue<PAIR> q_ue;
for(int i=0;i!=MAXN;++i)
{
for(int j=0;j!=MAXN;++j)
{
save[i][j] = INF;
}
}
q_ue.push(PAIR(0,0));
save[0][0] = 0;
while(!q_ue.empty())
{
PAIR p = q_ue.front();
q_ue.pop();
if(p.first==p.second&&p.second==MAXN-1)
{
break;
}
for(int i=0;i!=4;++i)
{
int n_x = p.first+dir[i][0],n_y = p.second+dir[i][1];
if(n_x>=0&&n_x<MAXN&&n_y>=0&&n_y<MAXN&&save[n_x][n_y]==INF&&maze[n_x][n_y]==0)
{
//当前步可走,入队
q_ue.push(PAIR(n_x,n_y));
//更新最小步数
save[n_x][n_y] = save[p.first][p.second] + 1 ;
}
}
}
return save[MAXN-1][MAXN-1] ;
}
int main()
{
for(int i=0;i!=MAXN;++i)
{
for(int j=0;j!=MAXN;++j)
{
cin >> maze[i][j] ;
}
}
int flag = bfs();
if(flag==INF)
{
cout << "不可行" << endl;
}
else
{
cout << "最小步数是:" << flag << endl;
}
return 0;
}
要打印所有的通路,那么,当我们从起点搜索到一条通路的时候,我们就得把它打印出来,而我们从哪里去打印这条通路,这就需要我们事先把搜索到的这条通路保存起来,下面附上具体代码:
#include<iostream>
using namespace std;
//迷宫的维数
#define MAXN 5
int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};
int maze[MAXN][MAXN];
int path[MAXN*MAXN*2];
void dfs(int depth,int x,int y)
{
//0代表可行,1代表不可行
if(x>=0&&x<MAXN&&y>=0&&y<MAXN&&maze[x][y]==0)
{
path[2*depth] = x,path[2*depth+1] = y;
if(x==y&&y==MAXN-1)
{
//如果找到一条通路,那么就将其打印出来
for(int i=0;i!=2*(depth+1);i+=2)
{
cout << "(" << path[i] << "," << path[i+1] << ")" << endl;
}
cout << endl;
}
maze[x][y] = 1;
for(int i=0;i!=4;++i)
{
dfs(depth+1,x+dir[i][0],y+dir[i][1]);
}
maze[x][y] = 0;
}
}
int main()
{
for(int i=0;i!=MAXN;++i)
{
for(int j=0;j!=MAXN;++j)
{
cin >> maze[i][j] ;
}
}
dfs(0,0,0);
return 0;
}
3.打印起点到终点的一条最短路径.
其实,这个问题,可以看成是上面两个小问题的一个综合而已,无非有点特殊的就是,我们要打印最短路径,而path里面保存的只是当前的一条通路(如果存在的话),而我们并非知道这条路径是否是最终要打印的路径,所以我们需要另外定义一个数组来保存path数组里面的值,具体细节,请看代码:
#include<iostream>
using namespace std;
#define MAXN 5
int maze[MAXN][MAXN];
int path[MAXN*MAXN*2],path1[MAXN*MAXN*2];
int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};
int step_min = 10000;
void dfs(int depth,int x,int y)
{
if(x==y&&y==MAXN-1)
{
if(depth < step_min)
{
step_min = depth ;
for(int i=0;i!=2*depth;++i)
{
path1[i] = path[i];
}
}
return ;
}
for(int i=0;i!=4;++i)
{
int n_x = x + dir[i][0],n_y = y + dir[i][1] ;
if(n_x>=0&&n_x<MAXN&&n_y>=0&&n_y<MAXN&&maze[n_x][n_y]==0)
{
maze[n_x][n_y] = 1;
path[2*depth]=n_x,path[2*depth+1] = n_y ;
dfs(depth+1,n_x,n_y);
maze[n_x][n_y] = 0;
}
}
}
int main()
{
for(int i=0;i!=MAXN;++i)
{
for(int j=0;j!=MAXN;++j)
{
cin >> maze[i][j];
}
}
path[0] = 0,path[1] = 0;
dfs(1,0,0);
if(step_min==10000)
{
cout << "不可行" << endl;
}
else
{
for(int i=0;i!=2*step_min;i+=2)
{
cout << "(" << path1[i] << ", " << path1[i+1] << ")" << endl;
}
}
return 0;
}
其实,打印最短的一条路径,更好的做法是采用广搜,因为广搜搜出来的就是一条最短路径,而深搜必须要把可走的路径都走了,才知道哪条路径是最短的,所以从效率上看,在这种情况下,广搜明显更具有优势,下面附上具体代码:
#include<iostream>
#include<queue>
#include<utility>
#include<ctime>
#include<cstdlib>
using namespace std;
#define M 10
#define INF -1
int dis[M][M],path[M][M];
//搜索方向
int dir[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
int maze[M][M];
//点的类型
typedef pair<int,int> P_INT;
P_INT s,e;
//递归打印路径
void print(P_INT t)
{
if(t==s)
{
cout << "(" << t.first << "," << t.second << ")\t";
return ;
}
int i = path[t.first][t.second];
print(P_INT(t.first-dir[i][0],t.second-dir[i][1]));
cout << "(" << t.first << "," << t.second << ")\t";
}
//广度优先搜索
void bfs()
{
queue<P_INT> q;
//初始化
for(int i=0;i!=M;++i)
{
for(int j=0;j!=M;++j)
{
dis[i][j] = INF;
}
}
q.push(s);
dis[s.first][s.second] = 0;
while(!q.empty())
{
P_INT temp = q.front();
q.pop();
if(temp==e)
{
break;
}
for(int i=0;i!=4;++i)
{
int new_x=temp.first+dir[i][0],new_y=temp.second+dir[i][1];
if(new_x>=0&&new_x<M&&new_y>=0&&new_y<M&&maze[new_x][new_y]==0&&dis[new_x][new_y]==INF)
{
q.push(P_INT(new_x,new_y));
dis[new_x][new_y] = dis[temp.first][temp.second]+1;
path[new_x][new_y] = i;
}
}
}
if(dis[e.first][e.second]!=INF)
{
print(e);
cout << dis[e.first][e.second] << endl;
}
else
{
cout << "invalid." << endl;
}
}
int main()
{
srand((unsigned)time(NULL));
//随机生成迷宫,0代表可走,1代表是墙
for(int i=0;i!=M;++i)
{
for(int j=0;j!=M;++j)
{
maze[i][j] = rand()%2;
cout << maze[i][j] << " " ;
}
cout << endl;
}
//输入起点
cin >> s.first >> s.second ;
//输入终点
cin >> e.first >> e.second ;
bfs();
return 0;
}
对于深搜问题,我们大部分还是把它看成是树的一种形态,不过,有些路有相应的限制条件。看了上面的,只是对深搜一个初步的了解,如果你还感兴趣的话,可以到poj上面去做一些关于深搜的题目,那样,对你理解深搜会更有帮助。