1. 程式人生 > >深度優先搜尋:能否走出迷宮

深度優先搜尋:能否走出迷宮

題目

一個m×n的迷宮裡有很多鐳射,用其起點和終點座標(x1,y1,x2,y2)來表示。有鐳射的地方不能通過。當起點為(0,0),終點為(m,n),且移動只能為上下左右各一個單位,問能否從起點到達終點。輸入包括mn,接著是鐳射的個數,每個鐳射的使用4個座標表示。如果能夠走出迷宮輸出1,否則輸出0。

maze

  • 測試輸入
    100 80
    3
    10 2 8 25
    44 12 6 6
    5 6 10 51
  • 測試輸出
    1

分析

如果能走出迷宮,只需要找到其中的一條路徑即可,所以適合用深度優先搜尋。而廣度優先搜尋會去找所有路徑中最短的,所以會在較大規模的迷宮中消耗較長的時間。
深度優先搜尋在此問題中就是一種回溯法,具體思路是
1. 一旦搜尋到一個可行點,立刻按照縱深

方向搜尋下一個可行點,如果這個點就是終點,則可以走出迷宮返回成功;
2. 每個可行點都要進行標記,保證之後不重複走已經標記的可行點;
3. 一旦發現當前點沒有下一個可行點時,就從當前點退回到上一個可行點,從上一個可行點繼續搜尋可行點;
4. 如果上一個可行點仍然沒有可行點,則一直退回直到某個最近點可以繼續搜尋可行點;
5. 如果一直退回到起點都不能搜尋到下一個可行點,那麼不能走出迷宮返回失敗。

深度優先搜尋可以使用遞迴的方式簡單實現,而非遞迴的方式更能展現其搜尋軌跡。非遞迴過程需要使用來儲存搜尋點,一旦搜尋到可行點立刻入棧,並從此點縱深搜尋下一個可行點;而如果沒有下一個可行點,則當前點出棧,從上一個可行點繼續搜尋。

具體到本題,需要將鐳射經過的地方都標記為不可行點,然後按照深度優先搜尋來尋找一條路徑。但鐳射是一條直線不好表示為不可行點,我們可以將直線轉化為一條向上凸起的折線,這樣的折線只有兩種形態,可見這樣的轉化並不會改變最後結果。

piecewise

程式碼

import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Scanner;
import java.util.Stack;

public class LaserMaze {
    static final int[][] MOVES = new int[][]{{0
, 1}, {1, 0}, {0, -1}, {-1, 0}};// 優先向右下方向前進 static class Pos { int x; int y; Pos(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object obj) { return ((Pos) obj).x == this.x && ((Pos) obj).y == this.y; } } // 廣度優先搜尋會超時 static int bfs(Pos start, Pos end, boolean[][] visited, int m, int n) { Queue<Pos> queue = new ArrayDeque<>(m * n); queue.add(start); Pos cur; while (!queue.isEmpty()) { cur = queue.remove(); if (cur.equals(end)) return 1; visited[cur.x][cur.y] = true; for (int[] step : MOVES) { int x = cur.x + step[0]; int y = cur.y + step[1]; if (x >= 0 && x <= n && y >= 0 && y <= m && !visited[x][y]) { Pos nextPos = new Pos(x, y); queue.add(nextPos); } } } return 0; } // 深度優先搜尋遞迴實現 static int dfs(Pos cur, Pos end, boolean[][] visited, int m, int n) { if (cur.equals(end)) return 1;// 找到一條路徑就返回 visited[cur.x][cur.y] = true;// 先對當前點標記,對已經走過的位置不重複走 for (int[] step : MOVES) { int x = cur.x + step[0]; int y = cur.y + step[1]; if (x >= 0 && x <= n && y >= 0 && y <= m && !visited[x][y]) { Pos next = new Pos(x, y); int ret = dfs(next, end, visited, m, n);// 對下一個可行點立刻縱深搜尋 if (ret == 1) return 1;// 如果找到一條路徑則立即返回,否則尋找下一個可行點 } } return 0; } // 深度優先搜尋非遞迴實現 static int dfs2(Pos start, Pos end, boolean[][] visited, int m, int n) { Stack<Pos> stack = new Stack<>(); stack.push(start); visited[start.x][start.y] = true;// 入棧點立刻標記 Pos cur; boolean hasNext; while (!stack.isEmpty()) { cur = stack.peek(); if (cur.equals(end)) return 1;// 找到一條路徑就返回 hasNext = false;// 有沒有下一個可行點 for (int[] step : MOVES) { int x = cur.x + step[0]; int y = cur.y + step[1]; if (x >= 0 && x <= n && y >= 0 && y <= m && !visited[x][y]) { Pos next = new Pos(x, y); stack.push(next); visited[x][y] = true;// 入棧點立刻標記 hasNext = true; break;// 對找到的下一個可行點立刻縱向搜尋 } } if (!hasNext) stack.pop();// 沒有下一個可行點則當前點出棧 } return 0; } static void makeLaser(int[][] lasers, boolean[][] visited) { for (int[] aLaser : lasers) {// 保證折線是上凸的 if (aLaser[3] > aLaser[1]) { for (int i = aLaser[1]; i <= aLaser[3]; i++) { visited[aLaser[0]][i] = true; } for (int i = aLaser[0]; i <= aLaser[2]; i++) { visited[i][aLaser[3]] = true; } } else { for (int i = aLaser[0]; i <= aLaser[2]; i++) { visited[i][aLaser[3]] = true; } for (int i = aLaser[3]; i <= aLaser[1]; i++) { visited[aLaser[0]][i] = true; } } } } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int m = sc.nextInt(); int n = sc.nextInt(); int nlaser = sc.nextInt(); int[][] lasers = new int[nlaser][4]; boolean[][] visited = new boolean[n + 1][m + 1]; for (int i = 0; i < nlaser; i++) { int x1 = sc.nextInt(); int y1 = sc.nextInt(); int x2 = sc.nextInt(); int y2 = sc.nextInt(); if (x1 < x2) {// 保證laser[0]<laser[2] lasers[i][0] = x1; lasers[i][1] = y1; lasers[i][2] = x2; lasers[i][3] = y2; } else { lasers[i][0] = x2; lasers[i][1] = y2; lasers[i][2] = x1; lasers[i][3] = y1; } } makeLaser(lasers, visited); Pos start = new Pos(0, 0); Pos end = new Pos(n, m); System.out.println(dfs(start, end, visited, m, n)); } }