一.问题描述
现有一个迷宫,以S作为起点,G作为终点,#作为墙壁,. 作为可以通行的道路,求从起点到终点的路径。多条路径,分别输出。
例如,输入:( 为便于观看,下图做了处理,输入 4 4 表示迷宫的长度(X轴)和宽度(Y轴)
输出从S到G点的行走路径,要求不能绕圈,即走过的点不能再走。
结果如下:(表示有两条路线)
(1,2),(2,2),(3,2),(3,1),(3,0),
(1,2),(1,3),(2,3),(2,2),(3,2),(3,1),(3,0),
二. 问题解析
如果是人进行观察图像,很容易就能知道上图中的迷宫,可以一直向下走,然后左拐,OK!坐标(1,1)点位置,虽然也是可达的一个点,但从图像上很容易就可以看出,后面的路都是死的,也就是不可能通过这个点到达终点,但是计算机在运算时实际上,不可能知道这一点,它必须要经过一个尝试才知道这条路不通。这一点,大多数人其实也会经历这个过程,甚至于有的人会反复的犯同样的错误。
简而言之,计算机绘制路径,需要遍历所有可以走的路径,其中的一条路径或几条可以到达终点,就是我们需要的路径。
方式有很多种,下面我介绍一种利用递归遍历所有情况求解的方式,在图论中称作深度优先遍历。
基本思路就是,从初始状态,不断的转移最近的状态,如果不能再转移,就回退到上一步,继续进行状态转移。直到状态到达需要的状态就返回,甚至可以遍历到所有的状态。
对于上图中,可以理解成,从S(开始点)不断遍历当前状态可达的最近状态,再由它能遍历的每一个下一个状态作为初始状态,再遍历可达的状态,也就是每一个状态只考虑自己当前可以达到什么状态,当然从整体上也需要有所约束,就像递归算法的思路,不断的将问题缩小成一个小问题,但是需要一个条件作为递归的最底层,从而跳出递归。
在迷宫问题中,显然一条不走重复路线经过点最多也就是是迷宫所拥有的点,或者某条线路已经到达了终点,就可以结束了,因此代码条件判断可以写为
// 如果已经到达终点,则遍历路线,并返回
if
(p.x==E.x&& p.y==E.y){
for
(
int
i=
0
;i<index;++i){
System.out.print(
"("
+fullPath[i].x+
","
+fullPath[i].y+
"),"
);
}
System.out.println();
return
;
}
// 如果探索的路线已经达到可能的最大值,则返回
if
(index>=fullPath.length){
return
;
}
|
在迷宫中,我们可以想到的是,一个点如果已经经过,那么通过其他方式再回到这个点的时候是没有意义的,可以认为选择的路线是一个回路,也就是一条错误的道路。就需要剪枝操作,因此,我们需要一个变量记录,经过点的状态。
我们用一个布尔值记录每个点的状态,并且将每次遍历的时候经过的点,标记为已经经过。如下代码中valid表示是否经过,注意的是,在一次循环递归中,到结束的时候,已经表示这条道的所有可能的情况都已经遍历结束,需要将是否经过的标识为再次设置为未经过,为之后的遍历中提供可以经过的标识。
// 移动在迷宫的范围内,并且不是墙,还没有在之前的线路中经过
if
(nx>=
0
&&nx<N&&ny>=
0
&&ny<M&&map[nx][ny]!=
'#'
&&valid[p.x][p.y]==
false
){
// 设置为已经过
valid[p.x][p.y]=
true
;
fullPath[index]=
new
Point(nx,ny);
dfs(fullPath[index],index+
1
);
// 这种状态下的,所有可能状态都遍历完毕,回退到上一种状态中
valid[p.x][p.y]=
false
;
}
|
在dfs(Depth-First Search)算法中,需要将初始状态传递给参数。
例如:
// S 是起始点,0代表步数
dfs(S,
0
);
|
三.完整示例代码
3.1 JAVA源码
import
java.util.Scanner;
public
class
CopyOfMaze {
public
static
Scanner scanner=
new
Scanner(System.in);
class
Point{
int
x;
int
y;
Point(
int
x,
int
y){
this
.x=x;
this
.y=y;
}
}
/**
* 方向分别为 东,南,西,北四个方向
*/
int
[][] direction={{
1
,
0
},{
0
,
1
},{-
1
,
0
},{
0
,-
1
}};
char
[][] map;
Point S,E;
public
void
dataInput(){
// 初始化地图
map=
new
char
[N][M];
for
(
int
i=
0
;i<N;++i){
String buff = scanner.nextLine();
for
(
int
j=
0
;j<M;++j){
map[i][j]=buff.charAt(j);
if
(map[i][j]==
'S'
){
S=
new
Point(i,j);
}
else
if
(map[i][j]==
'G'
){
E=
new
Point(i,j);
}
}
}
}
int
N,M;
Point[] fullPath;
boolean
[][] valid;
public
void
init(){
fullPath=
new
Point[N*M];
valid=
new
boolean
[N][M];
}
public
void
dfs(Point p,
int
index){
// 如果已经到达终点,则遍历路线,并返回
if
(p.x==E.x&& p.y==E.y){
for
(
int
i=
0
;i<index;++i){
System.out.print(
"("
+fullPath[i].x+
","
+fullPath[i].y+
"),"
);
}
System.out.println();
return
;
}
// 如果探索的路线已经达到可能的最大值,则返回
if
(index>=fullPath.length){
return
;
}
for
(
int
i=
0
;i<direction.length;++i){
// 移动向量
int
nx=p.x+direction[i][
0
];
int
ny=p.y+direction[i][
1
];
// 移动在迷宫的范围内,并且不是墙,还没有在之前的线路中经过
if
(nx>=
0
&&nx<N&&ny>=
0
&&ny<M&&map[nx][ny]!=
'#'
&&valid[p.x][p.y]==
false
){
// 设置为已经过
valid[p.x][p.y]=
true
;
fullPath[index]=
new
Point(nx,ny);
dfs(fullPath[index],index+
1
);
// 这种状态下的,所有可能状态都遍历完毕,回退到上一种状态中
valid[p.x][p.y]=
false
;
}
}
}
public
void
solve(){
dataInput();
init();
dfs(S,
0
);
}
public
static
void
main(String[] args){
CopyOfMaze maze=
new
CopyOfMaze();
maze.N=scanner.nextInt();
maze.M=scanner.nextInt();
scanner.nextLine();
maze.solve();
}
}
|
3.2 测试用例