算法设计与分析 | 迷宫搜索(蓝桥杯Java)

👾问题

下图给出了一个迷宫的平面图,其中标记为 1 的为障碍,标记为 0 的为可以通行的地方。

010000
000100
001001
110000

迷宫的入口为左上角,出口为右下角,在迷宫中,只能从一个位置走到这 个它的上、下、左、右四个方向之一。

对于上面的迷宫,从入口开始,可以按 DRRURRDDDR 的顺序通过迷宫, 一共 10 步。其中DULR 分别表示向下、向上、向左、向右走。 对于下面这个更复杂的迷宫(30 行 50 列),请找出一种通过迷宫的方式,其使用的步数最少,在步数最少的前提下,请找出字典序最小的一个作为答案。

请注意在字典序中 D<L<R<U

01010101001011001001010110010110100100001000101010
00001000100000101010010000100000001001100110100101
01111011010010001000001101001011100011000000010000
01000000001010100011010000101000001010101011001011
00011111000000101000010010100010100000101100000000
11001000110101000010101100011010011010101011110111
00011011010101001001001010000001000101001110000000
10100000101000100110101010111110011000010000111010
00111000001010100001100010000001000101001100001001
11000110100001110010001001010101010101010001101000
00010000100100000101001010101110100010101010000101
11100100101001001000010000010101010100100100010100
00000010000000101011001111010001100000101010100011
10101010011100001000011000010110011110110100001000
10101010100001101010100101000010100000111011101001
10000000101100010000101100101101001011100000000100
10101001000000010100100001000100000100011110101001
00101001010101101001010100011010101101110000110101
11001010000100001100000010100101000001000111000010
00001000110000110101101000000100101001001000011101
10100101000101000000001110110010110101101010100001
00101000010000110101010000100010001001000100010101
10100001000110010001000010101001010101011111010010
00000100101000000110010100101001000001000000000010
11010000001001110111001001000011101001011011101000
00000110100010001000100000001000011101000000110011
10101000101000100010001111100010101001010000001000
10000010100101001010110000000100101010001011101000
00111100001000010000000110111000000001000000001011
10000001100111010111010001000110111010101101111000

👾解题

📌方法1:

  • 思路:

  • BFS -- 首先从出口开始广度优先搜索,使用队列记录点坐标,同时使用数组记录当前位置和出口的距离,用于后面的深度优先搜索。

  • DFS -- 然后从入口开始深度优先搜索,如果下一个位置距离出口位置比当前位置少1,则为出口方向。

  • 代码:

import java.util.LinkedList;
import java.util.Queue;

/**
 * 下图给出了一个迷宫的平面图,其中标记为 1 的为障碍,标记为 0 的为可以通行的地方。
 * <p> 010000 </p>
 * <p> 000100 </p>
 * <p> 001001 </p>
 * <p> 110000 </p>
 * 迷宫的入口为左上角,出口为右下角,在迷宫中,只能从一个位置走到这 个它的上、下、左、右四个方向之一。
 * <p> 对于上面的迷宫,从入口开始,可以按 DRRURRDDDR 的顺序通过迷宫, 一共 10 步。
 * <p> 其中D、U、L、R 分别表示向下、向上、向左、向右走。 对于下面这个更复杂的迷宫,请找出一种通过迷宫的方式,其使用的步数最少,在步数最少的前提下,请找出字典序最小的一个作为答案。
 * <p> 请注意在字典序中D < L < R < U。
 **/

public class MazeSearch {
    private static final String[] mazeString = {
            "01010101001011001001010110010110100100001000101010",
            "00001000100000101010010000100000001001100110100101",
            "01111011010010001000001101001011100011000000010000",
            "01000000001010100011010000101000001010101011001011",
            "00011111000000101000010010100010100000101100000000",
            "11001000110101000010101100011010011010101011110111",
            "00011011010101001001001010000001000101001110000000",
            "10100000101000100110101010111110011000010000111010",
            "00111000001010100001100010000001000101001100001001",
            "11000110100001110010001001010101010101010001101000",
            "00010000100100000101001010101110100010101010000101",
            "11100100101001001000010000010101010100100100010100",
            "00000010000000101011001111010001100000101010100011",
            "10101010011100001000011000010110011110110100001000",
            "10101010100001101010100101000010100000111011101001",
            "10000000101100010000101100101101001011100000000100",
            "10101001000000010100100001000100000100011110101001",
            "00101001010101101001010100011010101101110000110101",
            "11001010000100001100000010100101000001000111000010",
            "00001000110000110101101000000100101001001000011101",
            "10100101000101000000001110110010110101101010100001",
            "00101000010000110101010000100010001001000100010101",
            "10100001000110010001000010101001010101011111010010",
            "00000100101000000110010100101001000001000000000010",
            "11010000001001110111001001000011101001011011101000",
            "00000110100010001000100000001000011101000000110011",
            "10101000101000100010001111100010101001010000001000",
            "10000010100101001010110000000100101010001011101000",
            "00111100001000010000000110111000000001000000001011",
            "10000001100111010111010001000110111010101101111000"};

    /**
     * 根据 字符串数组迷宫 转换为 整型数组迷宫
     */
    private static final char[][] maze = new char[30][50];

    /**
     * 记录迷宫每个位置距离出口的位置
     */
    private static final int[][] distance = new int[30][50];

    /**
     * 方位,字典序 D < L < R < U
     */
    private static final char[] direction = {'D', 'L', 'R', 'U'};

    /**
     * 方位,用于计算
     */
    private static final int[][] step = { // 右下↘为1 , 左上↖为-1
            {1, 0},     // D 下 1
            {0, -1},    // L 左 -1
            {0, 1},     // R 右 1
            {-1, 0}};   // U 上 -1

    /**
     * 输出的路径结果
     */
    private static final StringBuffer route = new StringBuffer();

    /**
     * 队列保存 广度优先搜索 经过的每一个点位置信息
     * <p>表示方法:当前位置所在行 * 每行的个数 + 当前位置所在列
     */
    private static final Queue<Integer> location = new LinkedList<>();



    /**
     * 广度优先遍历
     * <p>寻找所有从终点到起点的路线,求每一个位置到终点的距离,并存放在direction中
     */
    public static void bfs() {
        int nowX, nowY;
        while (!location.isEmpty()) { // 不为空,继续循环
            int now = location.poll();
            nowX = now / 50; // 获取当前位置x行
            nowY = now % 50; // 获取当前位置y列

            for (int i = 0; i < 4; i++) {
                int nextX = nowX + step[i][0]; // step[i][0] 按照D L R U 的次序
                int nextY = nowY + step[i][1]; // step[i][1] 按照D L R U 的次序

                // nextX >= 0 && nextX < 30 位置合法
                // nextY >= 0 && nextY < 50 位置合法
                // maze[nextX][nextY] == '0' 不是墙壁
                // distance[nextX][nextY] == 0 没有被访问过(distance初始化为0,所以没有另设数组)
                if ((nextX >= 0 && nextX < 30) && (nextY >= 0 && nextY < 50) &&
                        maze[nextX][nextY] == '0' && distance[nextX][nextY] == 0) {

                    // 当前位置的距离+1等于本次探索位置的距离
                    distance[nextX][nextY] = distance[nowX][nowY] + 1;
                    // 队列保存本次判断的位置,用于后面的广度优先搜索
                    location.add(nextX * 50 + nextY);
                    // 若到入口,则退出
                    if (nextX == 0 && nextY == 0) break;
                }
            }
        }
    }


    /**
     * 深度优先搜索
     * 从起点到终点遍历得到路径(不是传统意义上的DFS)
     */
    public static void dfs() {
        distance[29][49] = 0;
        int nowX = 0;
        int nowY = 0;
        while (nowX != 29 || nowY != 49) {
            for (int i = 0; i < 4; i++) {
                int nextX = nowX + step[i][0];
                int nextY = nowY + step[i][1];

                // nextX >= 0 && nextX < 30 位置合法
                // nextY >= 0 && nextY < 50 位置合法
                // maze[nextX][nextY] == '0' 不是墙壁
                if (nextX >= 0 && nextX < 30 && nextY >= 0 && nextY < 50 && maze[nextX][nextY] == '0') {
                    // 下一个位置距离出口位置比当前位置少1,则为出口方向
                    if (distance[nowX][nowY] == distance[nextX][nextY] + 1) {
                        nowX = nextX;
                        nowY = nextY;
                        route.append(direction[i]);
                        break;
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        // 读入迷宫
        for (int i = 0; i < 30; i++)
            maze[i] = mazeString[i].toCharArray();

        // 从出口往入口搜索,添加终点坐标,29 * 50 + 49表示位置
        location.add(29 * 50 + 49);

        bfs(); // 对迷宫进行广度优先搜索
        dfs(); // 根据 广度优先搜索的结果 深度优先搜索 路径
        System.out.println(route);
    }
}

📌方法2

  • 思路:

以左上角为原点建立坐标系,(x,y)表示每个点,如下:

  • BFS -- 首先,从入口开始进行广度优先搜索,使用一个二维数组记录下每个坐标是从上一个坐标的哪个方位得来的

如入口处第一个点(0,0)是从入口向下得到的,则标记为 D 。(0,1)是从(0,0)向下得到的,则标记为 D ... ... 这样不断标记各点,直到出口时停止。

  • DFS -- 然后,从出口处深度优先搜索,由于BFS已经标记了 每个坐标是从上一个坐标的哪个方位得来的,故可以从出口逆向反推出整个路径。

如出口第一个点(5,3)出发,其标记为R,即它是从上一个点(4,3)往右走得到的,故我们往左边走,即可得到(4,3);根据(4,3)标记的D,逆向往上走,即可得到(4,2) ... ... 这样不断的回溯,同时记录方位,最后便可以得到 路径。

  • 代码:

import java.util.LinkedList;
import java.util.Queue;

public class MazeSearch2 {
    private static final String[] mazeString = {
            "01010101001011001001010110010110100100001000101010",
            "00001000100000101010010000100000001001100110100101",
            "01111011010010001000001101001011100011000000010000",
            "01000000001010100011010000101000001010101011001011",
            "00011111000000101000010010100010100000101100000000",
            "11001000110101000010101100011010011010101011110111",
            "00011011010101001001001010000001000101001110000000",
            "10100000101000100110101010111110011000010000111010",
            "00111000001010100001100010000001000101001100001001",
            "11000110100001110010001001010101010101010001101000",
            "00010000100100000101001010101110100010101010000101",
            "11100100101001001000010000010101010100100100010100",
            "00000010000000101011001111010001100000101010100011",
            "10101010011100001000011000010110011110110100001000",
            "10101010100001101010100101000010100000111011101001",
            "10000000101100010000101100101101001011100000000100",
            "10101001000000010100100001000100000100011110101001",
            "00101001010101101001010100011010101101110000110101",
            "11001010000100001100000010100101000001000111000010",
            "00001000110000110101101000000100101001001000011101",
            "10100101000101000000001110110010110101101010100001",
            "00101000010000110101010000100010001001000100010101",
            "10100001000110010001000010101001010101011111010010",
            "00000100101000000110010100101001000001000000000010",
            "11010000001001110111001001000011101001011011101000",
            "00000110100010001000100000001000011101000000110011",
            "10101000101000100010001111100010101001010000001000",
            "10000010100101001010110000000100101010001011101000",
            "00111100001000010000000110111000000001000000001011",
            "10000001100111010111010001000110111010101101111000"};

    /**
     * 根据 字符串数组迷宫 转换为 整型数组迷宫
     */
    private static final char[][] maze = new char[30][50];

    /**
     * 方位,字典序 D < L < R < U
     */
    private static final char[] direction = {'D', 'L', 'R', 'U'};

    /**
     * 方位,用于计算
     */
    private static final int[][] step = { // 右下↘为1 , 左上↖为-1
            {1, 0},     // D 下 1
            {0, -1},    // L 左 -1
            {0, 1},     // R 右 1
            {-1, 0}};   // U 上 -1

    /**
     * 记录每个位置 是否被访问过
     * <p>用 1 表示访问过,0 表示没有访问过</p>
     */
    private static final int[][] visitFlag = new int[30][50];

    /**
     * 记录每个位置 是从上一个坐标的哪个方位得来的
     * <p>用 i 记录,其中 direction[i]依次代表 D、L、R、U</p>
     */
    private static final int[][] directionRemember = new int[30][50];

    /**
     * 队列保存 广度优先搜索 经过的每一个点位置信息
     * <p>点坐标表示方法:当前位置所在行 * 每行的个数 + 当前位置所在列
     */
    private static final Queue<Integer> location = new LinkedList<>();

    /**
     * 广度优先遍历
     * <p>寻找所有从终点到起点的路线,求每一个位置到终点的距离,并存放在direction中</p>
     */
    public static void bfs() {
        int nowX, nowY;
        while (!location.isEmpty()) {
            int now = location.poll();
            nowX = now / 50;
            nowY = now % 50;

            for (int i = 0; i < 4; i++) {
                int nextX = nowX + step[i][0]; // step[i][0] 按照 D L R U 的次序
                int nextY = nowY + step[i][1]; // step[i][1] 按照 D L R U 的次序

                // nextX >= 0 && nextX < 30 位置合法
                // nextY >= 0 && nextY < 50 位置合法
                // maze[nextX][nextY] == '0' 不是墙壁
                // visitFlag[nextX][nextY] == 0 没有被访问过
                if ((nextX >= 0 && nextX < 30) && (nextY >= 0 && nextY < 50) &&
                        maze[nextX][nextY] == '0' && visitFlag[nextX][nextY] == 0) {

                    // visitFlag记录访问过
                    visitFlag[nextX][nextY] = 1;
                    // directionRemember 记住该坐标是从上一个坐标的哪个方位得来的
                    directionRemember[nextX][nextY] = i;
                    // 队列保存本次判断的位置,用于后面的广度优先搜索
                    location.add(nextX * 50 + nextY);
                    // 若到入口,则退出
                    if (nextX == 0 && nextY == 0) break;
                }
            }
        }
    }

    /**
     * 深度优先搜索
     * <p>寻找所有从终点到起点的路线,根据directionRemember的方位记录(R、L、D、U),依次找到路径</p>
     * @param start 从终点开始搜索
     * @return 到当前点的 route 串
     */
    public static String dfs(int start) {
        int nowX = start / 50;
        int nowY = start % 50;
        String route = "";
        if (nowX != 0 || nowY != 0) {
            // i 为该坐标是从上一个坐标的哪个方位来的
            int i = directionRemember[nowX][nowY];
            // 相减,得到上一个坐标点
            int nextX = nowX - step[i][0];
            int nextY = nowY - step[i][1];
            // 记录当前点的 route 串
            route = dfs(nextX * 50 + nextY) + direction[i];
        }
        return route;
    }

    public static void main(String[] args) {
        // 读入迷宫
        for (int i = 0; i < 30; i++)
            maze[i] = mazeString[i].toCharArray();

        // 从入口往出口广度优先搜索,添加起点坐标,0 * 50 + 0表示位置
        location.add(0 * 50 + 0);
        bfs();
        String route = dfs(29 * 50 + 49);
        System.out.println(route);
    }
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值