最近在学习算法,又又看到了一个挺有趣的题目,就是走迷宫,就写了一篇简单实现走迷宫的算法,就只是找到一条出路,你问为啥不写复杂一点,因为作者想留给你们写(就是懒得考虑那么多dog)。如果有小伙伴需要考虑复杂一些的,比如输出多条路线,或者多条路线找到最简等要求,可以给我留言,我在出一个2.0迷宫(正大光明水文章)。
说明
走迷宫是递归求解的基本题型,我们在二维阵列中使用1表示迷宫墙壁,使用0表示行走的路径,找到从入口至出口的路线。
从第一行的第一个元素为入口,走到最后一行的最后一个元素为出口
0 1 1 1 0 0 0 1 0 1 0 0 0 1 1 0
* 1 1 1 这个就是路线 * * * 1 0 1 * * 0 1 1 *
提示:下一步进入我们最喜欢的解题思路环节。
解题思路
上面我们已经画出了行走路线,但是要怎么才能实现那?
1: 第一步
我们理所应当要先考虑怎么实现行走,这个很简单,我们只需要在原先的二维坐标上面进行操作,比如(a,b),我们给 a+1 就实现了向下,a-1 向上 ,b+1 向右,b-1 向左,这样就解决了行走。
2: 第二步
我们需要考虑边界问题,如果你不考虑,那么恭喜你将喜提ArrayIndexOutOfBoundsException
异常(数组索引越界异常)。因为是二维数组嘛,所以最小我们要考虑大于等于0,最大只能是小于数组的长度(数组的行和列)
3: 第三步 (核心)
这步也是核心,我用的是递归解法,不懂的小伙伴需要去了解一下递归的思想(有时间我也写一写递归)。除了第一步,后面的每一步我们都可以考虑最大有四种行走的可能,我们可以写四个方法,分别代表向上、下、左、右移动,行走后是否等于 0 ,也就是是否可以接着行走(可以接着行走,再次调用我们的方法),一直到最后每一个方法都不成立,达到递归返回的条件,这个时候无非两种情况,要么走到死胡同,要么成功了。成功,我们只需要判断这个下标是否为出口点就可以了,然后递返。如果是死胡同,就递返到有交叉出口的地方,也就是能调用两种及以上行走方法的地方,接着走下一条路线。(这里有点绕可以多看看,因为递归有点抽象,不好理解,也不好说)。
所以我画了一个图方便理解。
我们可以看到从起点开始,代替移动的四个方法只有向下满足,所以这时候只能向下移动,到分叉路口,这个时候移动会根据我们写的移动方法的顺序,接着移动,这里假如是向下的方法优先,就会先向下移动。走蓝色的路线,走到最下面,然后进行判断,走到死胡同了,然后触发我们设置的递归条件,进行递返,一直到交叉口的位置,走红色路线,然后进行判断,在进行递返。
注意!
我们需要留心的一个小细节,当我们走到一个点的时候,需要改变走过点的值,因为你到下一步,四种可能当中就一定会满足你刚刚走过的点,这样你就又走回去了。
代码
不要看代码多,很多都是每一步的注解,和一些数据的输出,关键代码不多
public class Test {
public static void main(String[] args) {
//走迷宫
String [][]source = {
{"0", "0", "1", "1", "1"},
{"1", "0", "1", "1", "0"},
{"0", "0", "0", "0", "0"},
{"1", "0", "1", "1", "0"},
{"0", "0", "1", "1", "0"},
{"0", "1", "0", "0", "0"},
{"0", "0", "0", "1", "0"}
};
//输出原始数据
for(int i = 0 ; i < source.length ; i++){
for(int j = 0 ; j < source[i].length ; j++){
System.out.print(source[i][j] + " ");
}
System.out.println();
}
//调用移动的方法 (0,0)为我们设置的起点
move(0, 0, source);
System.out.println("--------------");
for(int i = 0 ; i < source.length ; i++){
for(int j = 0 ; j < source[i].length ; j++){
System.out.print(source[i][j] + " ");
}
System.out.println();
}
}
public static int move(int a, int b, String [][]source){
//移动的四个方向
int ad = a + 1; //下
int ax = a - 1; //上
int bd = b + 1; //右
int bx = b - 1; //左
//判断是否满足边界条件
if(ad < source.length){
//下 如果能走下一步接着调用移动方法
if(source[ad][b] == "0"){
//给走过的路线标记 * 号避免,刚走了下一步,到下一个点又执行向上
source[a][b] = "*";
//如果等于 1 表示找到出口 然后一直返回(触发递归的条件)
if(move(ad, b, source) == 1){
source[a][b] = "-";
System.out.println(a + "," + b);
return 1;
}
}
}
if(ax >= 0){
//上
if(source[ax][b] == "0"){
source[a][b] = "*";
if(move(ax, b, source) == 1){
source[a][b] = "-";
System.out.println(a + "," + b);
return 1;
}
}
}
if(bd < source[a].length){
//右
if(source[a][bd] == "0"){
source[a][b] = "*";
if(move(a, bd, source) == 1){
source[a][b] = "-";
System.out.println(a + "," + b);
return 1;
}
}
}
if(bx >= 0){
//左
if(source[a][bx] == "0"){
source[a][b] = "*";
if( move(a, bx, source) == 1){
source[a][b] = "-";
System.out.println(a + "," + b);
return 1;
}
}
}
source[a][b] = "*";
//出口点
if(a == 5 && b == 4){
System.out.println("有出口");
//输出成功的每一个点
System.out.println(a + "," + b);
source[a][b] = "-";
//返回 1 代表成功 0 失败
return 1;
}
return 0;
}
}
结果
最下面带 - 就是我们的成功的路线,带 * 就是我们走过的但是不通的路线
0 0 1 1 1
1 0 1 1 0
0 0 0 0 0
1 0 1 1 0
0 0 1 1 0
0 1 0 0 0
0 0 0 1 0
有出口
5,4
5,3
5,2
6,2
6,1
6,0
5,0
4,0
4,1
3,1
2,1
1,1
0,1
0,0
--------------
- - 1 1 1
1 - 1 1 *
0 - * * *
1 - 1 1 *
- - 1 1 *
- 1 - - -
- - - 1 *
老规矩,这个算法只是作者自己的理解,可能有地方有问题或者bug(毕竟作者也就是一个老一点的菜鸟),如果有问题,可以留言和私信我,看到都会进行修改,让我们共同进步,一起成长 ^ - ^。