回溯法是一種不斷試探且及時糾正錯誤的搜索方法,下面的求解過程採用回溯法。從入口出發,按某一方向向前探索,若能走通(未走過的),即某處可以到達,則到達一個新點,否則試探下一個方向;若所有的方向均沒有通路,則沿原路返回前一點,換下一個方向繼續試探,直到所有可能的通路都搜索到,或找到一條通路,或無路可走又返回到入口點。這裏可以用一個棧來實現,每走一步,將該位置壓入棧中,若該點無路可走,則出棧返回上一位置。
總結與思路:
一般的簡單迷宮問題,類似於能否出去。可以使用“染色”的思路,一個節點被染紅之後傳給相鄰的節點,最後如果出口節點也被染紅,那麼可以出去。從代碼實現上看,
- 傳入當前節點的信息,每次方法開始,判斷是否爲出口。是就可以提前退出,不是則繼續向下執行。
- 建立迷宮大小的靜態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;
}
}
}
下面幾個網上的迷宮問題傳送門: