啊哈算法之解救小哈

简述

本算法摘选自啊哈磊所著的《啊哈!算法》第四章第二节的题目——DFS算法解救小哈。文中代码使用C语言编写,博主通过阅读和理解,重新由Java代码实现了一遍,以此来加深对DFS算法的印象。

游戏设置

迷宫由n行m列的单元格组成(n和m小于等于50),每个单元格要么是空地要么是障碍物,障碍物是不能通行的,要求从迷宫起点开始找到一条通往迷宫中某个点的最短路径。

解法思路

首先用一个二维数组来存储迷宫,刚开始假设从迷宫入口(0, 0)开始,目标位置在(q, p),此行目的就是找到从(0, 0)到(q, p)的最短路径。如果从入口的点,那么下一步只能是往右或者往下走,如果是到了迷宫中间的某个点,那么可能是往上下左右四个方向走,只能一个个去尝试,索性我们这里就定义个顺序,按照顺时针(即右、下、左、上)的方向逐个尝试

还有一个问题值得注意,就是已经路过的点,肯定是不需要计算在接下来尝试的点上了,这里为了方便判断,可以利用熟悉的桶来标记,已经经过的点都标记在桶里,使用完之后回来再清理桶标记位就行了。对于障碍物,遇到障碍物就换个方向重新开始,这里就是重新返回到下一次递归循环就行了。

注意,当我们遍历之后找到了目标点位,但可能不是到达所使用的最短路径,所以在找到目标点位之后,需要返回重新尝试从其他的方向寻找,直到把所有的可能性都尝试完了,最后才把最短的那条路径输出来。

现在我们使用深度优先搜索算法DFS来尝试实现这个算法。先从dfs函数开始,思考当前步骤是什么样的,下一步和当前步骤是一样的,边界条件是什么样的。

我们给dfs函数定义三个参数x、y和step,分别表示下一步达到的坐标以及当前步数,如果判断已经找到目标位置,即判断(x, y)是否与目标位置相等即可,同时用当前所走的step数去判断是否是最小步数,是则更新记录到最小步数值。

在具体的代码实现中,我们还应该关注到如何从顺时针的四个方向上开始搜索目标,如何定位到下一个点位,同时判断是否越界,或者是否是障碍物、或者该点位之前已经路过等等之类的问题。

代码实现

 1 public class MazeDfs {
 2 
 3     /** 迷宫矩阵的大小:n行 m列 */
 4     private static final Integer n = 10, m = 10;
 5     /** 迷宫中的目标位置 */
 6     private static final Integer q = 6, p = 6;
 7     /** 从起点到目标位置所行的最小步数 */
 8     private static Integer minStep = 999;
 9     /** 迷宫地图 */
10     private static int[][] maze = new int[50][50];
11     /** 桶,记录步行经过的路径位置 */
12     private static int[][] book = new int[50][50];
13 
14     /**
15      * 递归函数
16      * 往前走一步,查看当前位置是否目标位置,否则继续往下一个方向前进
17      * @param x
18      * @param y
19      * @param step
20      */
21     public void dfs(int x, int y, int step) {
22         // 定义四个方向,依照顺时针方向,依次向右、向下、向左、向上
23         int[][] next = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
24 
25         System.out.println("当前坐标点:" + x + " " + y);
26 
27         // 判断是否到达目标位置
28         if(x == q && y == p) {
29             // 更新最小步数值
30             if(step < minStep) {
31                 minStep = step;
32             }
33             // 按照当前方式找到,则返回,换其他方式继续找
34             return;
35         }
36 
37         // 枚举四个方向的找法
38         int tx, ty, k;
39         for(k = 0; k < 4; k++) {
40             // 计算出下一个步入的点的位置
41             tx = x + next[k][0];
42             ty = y + next[k][1];
43 
44             // 判断这个方向的下一步是否越界
45             if(tx < 0 || tx >= n || ty < 0 || ty >= m) {
46                 continue;
47             }
48 
49             // 判断该点是否为障碍物或者刚才已经路过
50             if(maze[tx][ty] == 0 && book[tx][ty] == 0) {
51                 // 标记这个点已经路过
52                 book[tx][ty] = 1;
53                 // 接着尝试下一个点
54                 dfs(tx, ty, step + 1);
55                 // 尝试结束,取消这个点的标记
56                 book[tx][ty] = 0;
57             }
58         }
59         return;
60     }
61 
62     public static void main(String[] args) {
63         MazeDfs mazeDfs = new MazeDfs();
64        // 初始化,把迷宫矩阵用二维数组表示出来,空地用“0”表示,障碍物用“1”表示
65         // TODO
66 
67         // 从起点开始搜索,这里假设起点位置[0, 0],开始时步数为0
68         int startx = 0, starty = 0, step = 0;
69         // 在桶中标记起点已经历
70         book[startx][starty] = 1;
71         // 从第一个位置开始
72         mazeDfs.dfs(startx, starty, step);
73 
74         // 输出最少步数
75         System.out.println(String.format("从起点开始最少经过%d步可以到达目标位置。", minStep));
76     }
77 
78 }

参考资料

1、《啊哈!算法》/ 啊哈磊著. 人民邮电出版社

点赞