回溯法是一种不断试探且及时纠正错误的搜索方法,下面的求解过程采用回溯法。从入口出发,按某一方向向前探索,若能走通(未走过的),即某处可以到达,则到达一个新点,否则试探下一个方向;若所有的方向均没有通路,则沿原路返回前一点,换下一个方向继续试探,直到所有可能的通路都搜索到,或找到一条通路,或无路可走又返回到入口点。这里可以用一个栈来实现,每走一步,将该位置压入栈中,若该点无路可走,则出栈返回上一位置。
总结与思路:
一般的简单迷宫问题,类似于能否出去。可以使用“染色”的思路,一个节点被染红之后传给相邻的节点,最后如果出口节点也被染红,那么可以出去。从代码实现上看,
- 传入当前节点的信息,每次方法开始,判断是否为出口。是就可以提前退出,不是则继续向下执行。
- 建立迷宫大小的静态boolean数组,标识每个节点是否已被染色,避免死循环。
- 将本节点染色,判断是否有上下左右节点,有且未被染色,则调用本方法并传入将该节点信息(即递归调用)。
- 方法调用完毕,判断出口是否已经被染色。
复杂迷宫问题,查找最短路径等。
- 基本步骤同上,需注意几个问题。
- 考察最短路径,不同路径之间可以有一段重复。不应该再使用全局的Boolean数组。可以考虑使用List.contains()方法避免有重复的点。
- 需要保存路径时,使用回溯法。一般应使用stack,本题使用的是list,在执行下一次方法前将本节点加入list。然后一直向前探索,如果到了出口则保存下来,没到就不保存。递归方法返回时应该将本节点移除list,因为如果没到出口说明此路不通,无需保存本节点,如果到了出口,则已经加到result中了,不需要在保存本节点了。都应当移除,再从上一级节点探索。
遇到滴滴笔试题,迷宫上加了些其他因素
import java.util.*;
/**
* @author XF 迷宫求路径,类似“染色”
*/
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 输入参数 测试:4 4 10 1 0 0 1 1 1 0 1 0 1 1 1 0 0 1 1
int n = sc.nextInt();
int m = sc.nextInt();
int p = sc.nextInt();
int[][] mi = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
mi[i][j] = sc.nextInt();
}
}
flag = new boolean[n][m];
List result = new ArrayList<>();
List tempList = new ArrayList<>();
// 调用计算方法,传入参数。获得result.
cc(0, 0, mi, result, tempList);
// 比较消耗精力最少的
int shortestNum = 0;
List temp = (List) result.get(0);
int min = (int) temp.get(temp.size() - 1);
if (result.size() > 1) {
for (int i = 1; i < result.size(); i++) {
temp = (List) result.get(i);
int tempMin = (int) temp.get(temp.size() - 1);
if (tempMin < min) {
min = tempMin;
shortestNum = i;
}
}
}
List r = (List) result.get(shortestNum);
r.remove(r.size() - 1);
for(int i=0; i<r.size() ;i++){
System.out.print(r.get(i)+",");
}
System.out.println("[0,3]");
}
// 辅助计算的静态变量
static boolean[][] flag; // 为数组每个元素设置标志,避免重复到达某点造成多次计算或死循环
static int pu; // pUsed 每个成功到达出口的序列计算消耗的体力值
/*
* 计算迷宫路径方法,主要使用递归调用每个节点的上下左右。上下左右节点又可以调用它的上下左右。 按消耗体力的数量及出口位置,优先顺序为上、右、下、左
* 传入参数,形参,即引用变量,地址不变,但指向的地址的值可以变化.
*/
public static void cc(int i, int j, int[][] mi, List result, List tempList) {
// flag[i][j] = true;
// //本题是考察最短路径,不同路径之间可以有一段重复。应使用list.contains()判断是否已包含此节点。
// 不管从哪条路到了出口,新建list存入结果,并保存pu
if (i == 0 && j == mi[0].length - 1) {
tempList.add(pu);
result.add(new ArrayList<>(tempList));
tempList.remove((Integer) pu);
}
/*
* up,从当前节点,考虑向上。如果上个节点有且没有使用,将上个节点作为参数传入方法。其他同理。
* 将当前节点信息(x,y)存入list,调用完方法移除!!!非常重要!!核心!!
* 因为不管是否到达出口,都说明已经探索完从这个节点伸出的路径了,应当移除,则可以从此节点上个节点继续探索。
* 到达了出口,已经保存了,该移除;没到,此路不通,移除。 根据需求应当使用stack,我使用不多就用list代替了。
*/
if (i > 0 && mi[i - 1][j] == 1
&& !tempList.contains("[" + (i - 1) + "," + j + "]")) {
tempList.add("[" + i + "," + j + "]");
pu += 3;
cc(i - 1, j, mi, result, tempList);
tempList.remove("[" + i + "," + j + "]");
pu -= 3;
}
// right
if (j < mi[0].length - 1 && mi[i][j + 1] == 1
&& !tempList.contains("[" + i + "," + (j + 1) + "]")) {
tempList.add("[" + i + "," + j + "]");
pu += 1;
cc(i, j + 1, mi, result, tempList);
tempList.remove("[" + i + "," + j + "]");
pu -= 1;
}
// down
if (i < mi.length - 1 && mi[i + 1][j] == 1
&& !tempList.contains("[" + (i + 1) + "," + j + "]")) {
tempList.add("[" + i + "," + j + "]");
cc(i + 1, j, mi, result, tempList);
tempList.remove("[" + i + "," + j + "]");
}
// left
if (j > 0 && mi[i][j - 1] == 1
&& !tempList.contains("[" + i + "," + (j - 1) + "]")) {
tempList.add("[" + i + "," + j + "]");
pu += 1;
cc(i, j - 1, mi, result, tempList);
tempList.remove("[" + i + "," + j + "]");
pu -= 1;
}
}
}
下面几个网上的迷宫问题传送门: