數據結構與算法之DFS/BFS解決掃雷問題

給你一個2D的字符矩陣作爲遊戲板。. ‘M’代表未發現的地雷 , ‘E’ 代表着一個未發現的空白區域, ‘B’ 代表一個沒有相鄰(上,下,左,右和所有4個對角線)地雷的空白方塊,數字(’1’至’8’)表示與這個顯示的方形相鄰的地雷數量,最後是’X’ 代表一個已發現的地雷。

現在給出所有未顯示的遊戲版(’M’或’E’)中的下一個點擊位置(行和列索引),根據以下規則顯示該位置後返回主板:

如果一個地雷(’M’)被揭開,那麼這個遊戲結束
如果沒有相鄰地雷的空方塊(’E’)被顯示出來,則將其改爲顯示空白(’B’),並且所有相鄰的未顯示的區域應該遞歸地顯示。
如果與至少有一個相鄰的地雷的方塊(’E’),則將其改爲一個數字(’1’至’8’),表示相鄰地雷的數量。
返回主板,當沒有更多區域可以被打開
Example 1:
Input:

[[‘E’, ‘E’, ‘E’, ‘E’, ‘E’],
[‘E’, ‘E’, ‘M’, ‘E’, ‘E’],
[‘E’, ‘E’, ‘E’, ‘E’, ‘E’],
[‘E’, ‘E’, ‘E’, ‘E’, ‘E’]]

Click : [3,0]

Output:

[[‘B’, ‘1’, ‘E’, ‘1’, ‘B’],
[‘B’, ‘1’, ‘M’, ‘1’, ‘B’],
[‘B’, ‘1’, ‘1’, ‘1’, ‘B’],
[‘B’, ‘B’, ‘B’, ‘B’, ‘B’]]

Explanation:

Example 2:
Input:

[[‘B’, ‘1’, ‘E’, ‘1’, ‘B’],
[‘B’, ‘1’, ‘M’, ‘1’, ‘B’],
[‘B’, ‘1’, ‘1’, ‘1’, ‘B’],
[‘B’, ‘B’, ‘B’, ‘B’, ‘B’]]

Click : [1,2]

Output:

[[‘B’, ‘1’, ‘E’, ‘1’, ‘B’],
[‘B’, ‘1’, ‘X’, ‘1’, ‘B’],
[‘B’, ‘1’, ‘1’, ‘1’, ‘B’],
[‘B’, ‘B’, ‘B’, ‘B’, ‘B’]]

Explanation:

Note:
輸入矩陣的高度和寬度的範圍是[1,50]。
點擊位置只會是未顯示的方塊(’M’或’E’),這也意味着輸入板至少包含一個可點擊的方塊。
輸入板不會是遊戲結束的一個階段(一些地雷被揭示)。
爲了簡單起見,在此問題中不應忽略不提及的規則。 例如,當遊戲結束時,您不需要顯示所有未發現的礦井,考慮任何情況下,您將贏得比賽或標記任何方格。

解決方法
BFS
DFS

BFS解決此問題

public char[][] updateBoard(char[][] board, int[] click) {
        // 獲取地圖的長寬
        int m = board.length;
        int n = board[0].length;
        // 廣度優先遍歷 利用隊列
        Queue<int[]> queue = new LinkedList<>();
        // 將起點加入隊列
        queue.add(click);
        // 棧不爲空 則未遍歷完全
        while (!queue.isEmpty()) {
            // 按照隊列先進先出的原則依次遍歷
            int[] poll = queue.poll();
            int row = poll[0], col = poll[1];
            // 點到炸彈區域
            if (board[row][col] == 'M') { // Mine
                board[row][col] = 'X';
            } else {
                // 點到非炸彈區域 可能是數字區域也可能是空白區域
                int count = 0;
                // 將該點的周圍全部訪問
                for (int i = -1; i < 2; i++) {
                    for (int j = -1; j < 2; j++) {
                        // 遍歷到自己
                        if (i == 0 && j == 0)
                            continue;
                        int r = row + i, c = col + j;
                        // 遍歷超出邊界
                        if (r < 0 || r >= m || c < 0 || c < 0 || c >= n)
                            continue;
                        // 發現有炸彈
                        if (board[r][c] == 'M' || board[r][c] == 'X')
                            count++;
                    }
                }

                // 如果周圍有炸彈則這個區域不是空白區域 停止BFS
                if (count > 0) {
                    // 該區域周圍的炸彈數量就是該區域的數字大小
                    board[row][col] = (char) (count + '0');
                    // 周圍無炸彈 是空白區域
                } else {
                    board[row][col] = 'B';
                    // 將該區域周圍全部遍歷
                    for (int i = -1; i < 2; i++) {
                        for (int j = -1; j < 2; j++) {
                            if (i == 0 && j == 0)
                                continue;
                            int r = row + i, c = col + j;
                            if (r < 0 || r >= m || c < 0 || c < 0 || c >= n)
                                continue;
                            // 發現未被訪問區域 加入隊列
                            if (board[r][c] == 'E') {
                                queue.add(new int[] { r, c });
                                board[r][c] = 'B';
                            }
                        }
                    }
                }
            }
        }
        return board;
    }

測試

        @Test
    public void test() {
        char[][] board = { { 'E', 'E', 'E', 'E', 'E' }, { 'E', 'E', 'M', 'E', 'E' }, { 'E', 'E', 'E', 'E', 'E' },
                { 'E', 'E', 'E', 'E', 'E' }, { 'E', 'E', 'E', 'E', 'E' }, { 'E', 'E', 'E', 'E', 'E' } };

        for (char[] cs : board) {
            for (char c : cs) {
                System.out.print(c + " ");
            }
            System.out.println();
        }
        System.out.println("-------------");
        int[] click = { 3, 0 };
        MinesweeperSolutionByBFS minesweeperSolution = new MinesweeperSolutionByBFS();
        char[][] updateBoard = minesweeperSolution.updateBoard(board, click);
        for (char[] cs : updateBoard) {
            for (char c : cs) {
                System.out.print(c + " ");
            }
            System.out.println();
        }
    }

結果

E E E E E 
E E M E E 
E E E E E 
E E E E E 
E E E E E 
E E E E E 
-------------
B 1 E 1 B 
B 1 M 1 B 
B 1 1 1 B 
B B B B B 
B B B B B 
B B B B B 

DFS方法


    public char[][] updateBoard(char[][] board, int[] click) {
        int m = board.length, n = board[0].length;
        int row = click[0], col = click[1];
        // 發現炸彈
        if (board[row][col] == 'M') {
            board[row][col] = 'X';
            // 不是炸彈
        } else {
            int count = 0;
            // 遍歷該區域周圍區域 查找是否有炸彈
            for (int i = -1; i < 2; i++) {
                for (int j = -1; j < 2; j++) {
                    if (i == 0 && j == 0)
                        continue;
                    int r = row + i, c = col + j;
                    if (r < 0 || r >= m || c < 0 || c < 0 || c >= n)
                        continue;
                    if (board[r][c] == 'M' || board[r][c] == 'X')
                        count++;
                }
            }
            // 周圍有炸彈 則該區域不是空白區域 停止 DFS
            if (count > 0) {
                board[row][col] = (char) (count + '0');
            } else {
                // 該區域是空白區域
                board[row][col] = 'B';
                // 遍歷此區域周圍區域
                for (int i = -1; i < 2; i++) {
                    for (int j = -1; j < 2; j++) {
                        if (i == 0 && j == 0)
                            continue;
                        int r = row + i, c = col + j;
                        if (r < 0 || r >= m || c < 0 || c < 0 || c >= n)
                            continue;
                        // 遇到沒有訪問的區域 開始遞歸訪問
                        if (board[r][c] == 'E')
                            updateBoard(board, new int[] { r, c });
                    }
                }
            }
        }

        return board;
    }

    // 非遞歸版 利用棧
    public char[][] updateBoardByStack(char[][] board, int[] click) {
        // 創建棧
        Stack<int[]> stack = new Stack<>();
        int m = board.length, n = board[0].length;
        // 將起始點壓入棧
        stack.push(click);
        // 棧不爲空
        while (!stack.isEmpty()) {
            int[] peek = stack.peek();
            int row = peek[0], col = peek[1];
            // 發現炸彈
            if (board[row][col] == 'M') {
                board[row][col] = 'X';
                stack.pop();
                // 不是炸彈
            } else {
                int count = 0;
                // 遍歷該區域周圍區域 查找是否有炸彈
                for (int i = -1; i < 2; i++) {
                    for (int j = -1; j < 2; j++) {
                        if (i == 0 && j == 0)
                            continue;
                        int r = row + i, c = col + j;
                        if (r < 0 || r >= m || c < 0 || c < 0 || c >= n)
                            continue;
                        if (board[r][c] == 'M' || board[r][c] == 'X')
                            count++;
                    }
                }
                // 周圍有炸彈 則該區域不是空白區域 停止 DFS
                if (count > 0) {
                    board[row][col] = (char) (count + '0');
                    stack.pop();
                } else {
                    // 該區域是空白區域
                    board[row][col] = 'B';
                    int[] findNext = findNext(row, col, board);
                    // 周圍沒有可以訪問的
                    if (findNext[0] == -1 && findNext[1] == -1)
                        stack.pop();
                    else {
                        stack.push(findNext);
                    }
                }
            }
        }
        return board;
    }
    //查找下一個可訪問邊
    int[] findNext(int row, int col, char[][] board) {
        int[] next = { -1, -1 };
        // 遍歷此區域周圍區域
        for (int i = -1; i < 2; i++) {
            for (int j = -1; j < 2; j++) {
                if (i == 0 && j == 0)
                    continue;
                int r = row + i, c = col + j;
                if (r < 0 || r >= board.length || c < 0 || c < 0 || c >= board[0].length)
                    continue;
                // 遇到沒有訪問的區域
                if (board[r][c] == 'E') {
                    next[0] = r;
                    next[1] = c;
                    return next;
                }
            }
        }
        return next;
    }

測試

    @Test
    public void test() {
        char[][] board = { { 'E', 'E', 'E', 'E', 'E' }, { 'E', 'E', 'M', 'E', 'E' }, { 'E', 'E', 'E', 'E', 'E' },
                { 'E', 'E', 'E', 'E', 'E' }, { 'E', 'E', 'E', 'E', 'E' }, { 'E', 'E', 'E', 'E', 'E' } };
        for (char[] cs : board) {
            for (char c : cs) {
                System.out.print(c + " ");
            }
            System.out.println();
        }
        System.out.println("-------------");
        int[] click = { 3, 0 };
        MinesweeperSolutionByDFS minesweeperSolution = new MinesweeperSolutionByDFS();
        // 遞歸版
        char[][] updateBoard = minesweeperSolution.updateBoard(board, click);
        // 非遞歸版
        char[][] updateBoardByStack = minesweeperSolution.updateBoardByStack(board, click);
        for (char[] cs : updateBoard) {
            for (char c : cs) {
                System.out.print(c + " ");
            }
            System.out.println();
        }
        System.out.println("-------------");
        for (char[] cs : updateBoardByStack) {
            for (char c : cs) {
                System.out.print(c + " ");
            }
            System.out.println();
        }
    }

結果
E E E E E 
E E M E E 
E E E E E 
E E E E E 
E E E E E 
E E E E E 
-------------
B 1 E 1 B 
B 1 M 1 B 
B 1 1 1 B 
B B B B B 
B B B B B 
B B B B B 
-------------
B 1 E 1 B 
B 1 M 1 B 
B 1 1 1 B 
B B B B B 
B B B B B 
B B B B B 

到此 掃雷問題差不多就解決了

水平有限 若有錯誤 歡迎指正

關於圖的相關知識可以參考

圖的表示

深度優先DFS

廣度優先BFS

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章