• 宽度优先搜索


    参考《挑战程序设计竞赛(第2版)》第34页-36页

    题目:

    给定一个大小为N*M的迷宫,由通道('.')和墙壁('#')组成,其中通道S表示起点,通道G表示终点,每一步移动可以达到上下左右中不是墙壁的位置。试求出起点到终点的最小步数。(本题假定迷宫是有解的)

    (N,M<=100)

    样例输入:

    10 10
    # S # # # # # # . #
    . . . . . . # . . #
    . # . # # . # # . #
    . # . . . . . . . .
    # # . # # . # # # #
    . . . . # . . . . #
    . # # # # # # # . #
    . . . . # . . . . .
    . # # # # . # # # .
    . . . . # . . . G #

    样例输出:

    22


    题解:

    宽度优先搜索按照距开始状态由近及远的顺序进行搜索,因此可以很容易地用来求最短路径、最少操作之类问题的答案。这个问题中,状态仅仅是目前所在位置的坐标,因此可以构造成pair或者编码成int来表达状态。当状态更加复杂时,就需要封装成一个类来表示状态了。转移的方式为四方向移动,状态数与迷宫的大小是相等的,所以复杂度是O(4×N×M)=O(N×M)。
    宽度优先搜索中,只要将已经访问过的状态用标记管理起来,就可以很好地做到由近及远的搜索。这个问题中由于要求最短距离,不妨用d[N][M]数组把最短距离保存起来。初始时用充分大的常数INF来初始化它,这样尚未到达的位置就是INF,也就同时起到了标记的作用。
    虽然到达终点时就会停止搜索,可如果继续下去直到队列为空的话,就可以计算出到各个位置的最短距离。此外,如果搜索到最后,d依然为INF的话,便可得知这个位置就是无法从起点到达的位置。
    在今后的程序中,使用像INF这样充分大的常数的情况还很多。不把INF当作例外,而是直接参与普通运算的情况也很常见。这种情况下,如果INF过大就可能带来溢出的危险。
    假设INF=2^31-1。例如想用d[nx][ny]=min(d[nx][ny],d[x][y]+1)来更新d[nx][ny],就会发生INF+1=-2^31的情况。这一问题中d[x][y]总不等于INF,所以没有问题。但是为了防止这样的问题,一般会将INF设为放大2~4倍也不会溢出的大小。
    因为要向4个不同方向移动,用dx[4]和dy[4]两个数组来表示四个方向向量。这样通过一个循环就可以实现四个方向移动的遍历。

    c++版本代码:

    #include <cstdio>
    #include <iostream>
    #include <queue>
    using namespace std;
    const int MAX_N = 100;
    const int MAX_M = 100;
    const int INF = 0x3f3f3f3f;
    typedef pair<int, int> P;
    char maze[MAX_N][MAX_M + 1];
    int N, M;
    int sx, sy; //起点的位置
    int gx, gy; //终点的位置
    
    int d[MAX_N][MAX_M];//储存起点到某一点的距离
    int dx[4] = { 1,0,-1,0 }, dy[4] = { 0,1,0,-1 }; //表明每次x和y方向的位移
    
    void bfs()
    {
        queue<P> que;
        for (int i = 0; i < N; i++)
            for (int j = 0; j < M; j++)
                d[i][j] = INF;	//初始化所有点的距离为INF
        que.push(P(sx, sy));
        d[sx][sy] = 0;	//从起点出发将距离设为0,并放入队列首端
    
        while (que.size()) //题目保证有路到终点,所以不用担心死循环
        {
            P p = que.front(); que.pop();//弹出队首元素
            if(p.first == gx&&p.second == gy) break; //已经到达终点则退出
    
            for (int i = 0; i < 4; i++)
            {
                int nx = p.first + dx[i];
                int ny = p.second + dy[i];//移动后的坐标
                //判断可移动且没到过
                if (0 <= nx&&nx < N
                    && 0 <= ny&&ny < M
                    &&maze[nx][ny] != '#'
                    &&d[nx][ny] == INF)//之前到过的话不用考虑,因为距离在队列中递增,肯定不会获得更好的解
                {
                    que.push(P(nx, ny));	//可以移动则设定距离为之前加一,放入队列
                    d[nx][ny] = d[p.first][p.second] + 1;
                }
            }
        }
    
    }
    
    int main()
    {
        scanf("%d %d",&N,&M);
        for (int n = 0; n < N; n++){
            for (int j = 0; j < M; j++)
            {
                scanf("%s",&maze[n][j]);
            }
        }
        for (int i = 0; i < N; i++)
            for (int j = 0; j < M; j++)
            {
                if (maze[i][j] == 'S')
                {
                    sx = i; sy = j;
                }
                if (maze[i][j] == 'G')
                {
                    gx = i; gy = j;
                }
            }
        bfs();
        printf("%d
    ",d[gx][gy]);
        return 0;
    }
    

     Java版代码

    package 宽度优先搜索;
    
    import java.util.LinkedList;
    import java.util.Queue;
    import java.util.Scanner;
    
    public class Main {
    	static	class Point{
    		int x;
    		int y;
    		public int getX() {
    			return x;
    		}
    		public void setX(int x) {
    			this.x = x;
    		}
    		public int getY() {
    			return y;
    		}
    		public void setY(int y) {
    			this.y = y;
    		}
    		public Point(int x, int y) {
    			this.x = x;
    			this.y = y;
    		}
    
    	}
    	public static void main(String[] args) {
    		Scanner sc = new Scanner(System.in);
    		int N = sc.nextInt();
    		int M = sc.nextInt();
    		String maze[][] = new String[N][M];
    		for (int i = 0; i < maze.length; i++) {
    			for (int j = 0; j < maze[0].length; j++) {
    				maze[i][j] = sc.next();
    			}
    		}
    
    		bfs(maze);
    	}
    
    	private static void bfs(String[][] maze) {
    		int INF=100000;
    		int N = maze.length;
    		int M = maze[0].length;
    		int res[][]=new int[N][M];
    		int sx = 0, sy = 0; // 起点的位置
    		int gx = 0, gy = 0; // 终点的位置
    		for (int i = 0; i < N; i++) {
    			for (int j = 0; j < M; j++) {
    				if (maze[i][j].equals("S")) {
    					sx = i;
    					sy = j;
    				}
    				if (maze[i][j].equals("G")) {
    					gx = i;
    					gy = j;
    				}
    			}
    		}
    		for (int i = 0; i < N; i++) {
    			for (int j = 0; j < M; j++) {
    				res[i][j]=INF;
    			}
    		}
    		Point start=new Point(sx, sy);
    		Queue<Point> queue=new LinkedList<>();
    		queue.add(start);
    		res[sx][sy]=0;//将起始点设置为0
    		while (!queue.isEmpty()) {
    			
    			int dx[] = { 1,0,-1,0 }, dy[] = { 0,1,0,-1 }; //表明每次x和y方向的位移
    			Point tmp=queue.poll();
    			if(tmp.x == gx&&tmp.y == gy) break; //已经到达终点则退出
    			for (int i = 0; i < dx.length; i++) {
    				int nx = tmp.x + dx[i];
    				int ny = tmp.y + dy[i];//移动后的坐标
    				if (0 <= nx&&nx < N
    						&& 0 <= ny&&ny < M
    						&&!maze[nx][ny].equals("#")
    						&&res[nx][ny] ==INF)//之前到过的话不用考虑,因为距离在队列中递增,肯定不会获得更好的解
    				{
    					 queue.add(new Point(nx, ny));    //可以移动则设定距离为之前加一,放入队列
    		             res[nx][ny] = res[tmp.x][tmp.y] + 1;
    				}
    			}
    		}
    		System.out.println(res[gx][gy]);
    	}
    
    }
    

      

     

    加油啦!加油鸭,冲鸭!!!
  • 相关阅读:
    按键精灵-3-按键精灵键盘鼠标命令
    Python-13-pass,del和exec
    STP-9-处理RSTP中的拓扑变化
    Python-12-简单推导
    Python-11-循环
    Python-10-条件和条件语句
    Qt qml listview 列表视图控件(下拉刷新、上拉分页、滚动轴)
    Qt qml pageview 左右滑动分页组件
    Qt qml treeview 树控件
    Qt qml 模拟iphone slide to unlock 的聚光动画文字效果
  • 原文地址:https://www.cnblogs.com/clarencezzh/p/10341024.html
Copyright © 2020-2023  润新知