深度优先搜索:能否走出迷宫

题目

一个 m × n m\times n m×n的迷宫里有很多激光,用其起点和终点坐标 ( x 1 , y 1 , x 2 , y 2 ) (x_1,y_1,x_2,y_2) (x1,y1,x2,y2)来表示。有激光的地方不能通过。当起点为 ( 0 , 0 ) (0,0) (0,0),终点为 ( m , n ) (m,n) (m,n),且移动只能为上下左右各一个单位,问能否从起点到达终点。输入包括 m m m n n n,接着是激光的个数,每个激光的使用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));
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值