宽度优先搜索

参考《挑战程序设计竞赛(第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]);
	}

}

  

 

加油啦!加油鸭,冲鸭!!!
原文地址:https://www.cnblogs.com/clarencezzh/p/10341024.html