下面介紹BFS求迷宮題的解法。
在迷宮題中,BFS一般用於求迷宮中起點到終點的最短路徑sp(DFS一般用於求迷宮中起點到終點的路徑總條數)
下面以經典例題爲例,給大家BFS算法求解迷宮題的模板,習題1中BFS算法中附有詳細註釋,後面的習題中沒有
習題1:BFS_走字符迷宮
給你一個n行m列的二維迷宮。‘S’表示起點,‘T’ 表示終點,’#’ 表示牆壁,’.’ 表示平地。
你需要從 ‘S’ 出發走到 ‘T’,每次只能上下左右走動,
並且不能走出地圖的範圍以及不能走到牆壁上。請你計算出走到終點需要走的最少步數。
輸入格式
第一行輸入n, m表示迷宮大小。【1≤n,m≤100】
接下來輸入n行字符串表示迷宮,每個字符串長度爲m。
(地圖保證有且僅有一個終點,一個起始點)
輸出格式
【輸出走到終點的最少步數,如果不能走到終點輸出−1,佔一行。】
/*
樣例1:
輸入
3 3
S.#
.#.
.#T
輸出
-1
樣例2:
輸入
5 5
S.###
#....
#.#.#
#.#.#
#.#.T
輸出
8
樣例3:
輸入
6 6
...S..
.#.##.
......
...##.
.#.#..
.T....
輸出
7
*/
import java.util.*;
public class BFS_走字符迷宮{
//要用Queue的時候,如果是用<Integer>,那每個點只能存一個數字
//由於座標至少要有x、y兩個數字,所以必須建新類node,然後Queue<node>
static class node{
int x;
int y;
int step; //step用於存放起點到當前座標的最少步數(即層數)
//在這裏還可以存其他某些內容
node(int x,int y,int step){
this.x=x;
this.y=y;
this.step=step;
}
}
static int MAXV=999;
static int n,m; //大小爲n*m的地圖
static int[][] map2=new int[MAXV][MAXV]; //二選一,輸入全爲數字的地圖
static char[][] map=new char[MAXV][MAXV]; //二選一,輸入含有字符的地圖
static boolean[][] inq=new boolean[MAXV][MAXV]; //判定該點是否進過隊
static int sp=-1; //最終結果shortest path,用於存最短路徑的長度。如果無路徑,返回-1
//move數組的元素順序,會決定遍歷時的順序,本例中順序爲“上下左右”
static int[][] move= {{-1,0},{1,0},{0,-1},{0,1}};
//bfs算法
public static void bfs(int x,int y) {
//如果有需要,也可以把queue放到方法外面,加上static,作爲類變量
Queue<node> q=new LinkedList<node>();
q.add(new node(x,y,0));
inq[x][y]=true;
while(!q.isEmpty()) {
//1、進來之後,要做什麼
node temp=q.poll();
// inq[temp.x][temp.y]=true; //錯誤,inq數組的含義爲結點是否已入過隊,而非結點是否已被訪問
//2、如果是終點,要做什麼
if(map[temp.x][temp.y]=='T') {
sp=temp.step;
return ;
}
//3、如果不是終點,要做什麼
for(int i=0;i<4;i++) { //循環4次,得到4個相鄰位置
//不能直接改變原結點中的x,y,path的值,要另設變量
int nx=temp.x+move[i][0];
int ny=temp.y+move[i][1];
int nstep=temp.step+1;
if(check(nx,ny)) {
q.offer(new node(nx,ny,nstep));
inq[nx][ny]=true; //注意,結點入隊時,要立即標記已入隊
}
}
}
}
//邊界條件和約束條件的判斷,約束條件由實際題目決定
public static boolean check(int x,int y) {
//注意是map[x][y]!='#',如果改成map[x][y]=='.',會忽略‘T’的情況
if(x>=0&&y>=0&&x<n&&y<m&&!inq[x][y]&&map[x][y]!='#')
return true;
else return false;
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
//不能在n,m之前寫int
n=sc.nextInt();
m=sc.nextInt();
int x=0,y=0;
for(int i=0;i<n;i++) {
String s=sc.next();
for(int j=0;j<s.length();j++) {
if(s.charAt(j)=='S') {
x=i;
y=j;
}
}
map[i]=s.toCharArray();
}
bfs(x,y);
// 如果跑完dfs後還需要進行其他某些操作,就在這裏寫
System.out.println(sp);
sc.close();
}
}
得到了BFS的模板之後,再來練習一下下面這道字符迷宮的例題
習題2:BFS_走字符迷宮2
描述
一個迷宮由n行m列格子組成,#格子裏有障礙物,不能走;. 格子是空地,可以走。
給定一個迷宮,求從左上角走到右下角最少需要走多少步(數據保證一定能走到)。
只能在水平方向或垂直方向走,不能斜着走。
/*
樣例輸入
5 5
..###
#....
#.#.#
#.#.#
#.#..
樣例輸出
9
*/
import java.util.*;
public class 走字符迷宮2bfs {
static class node{
int x;
int y;
int path;
node(int x,int y,int path){
this.x=x;
this.y=y;
this.path=path;
}
}
static int MAXV=999;
static int n,m;
static int sp=-1;
static char[][] map=new char[MAXV][MAXV];
static int[][] v=new int[MAXV][MAXV];
static int[][] move= {{0,1},{0,-1},{1,0},{-1,0}};
public static void bfs(int x,int y) {
Queue<node> q=new LinkedList<node>();
q.offer(new node(x,y,1));
while(!q.isEmpty()) {
node temp=q.poll();
v[temp.x][temp.y]=1;
if(temp.x==n-1&&temp.y==m-1) {
sp=temp.path;
return ;
}
for(int i=0;i<4;i++) {
int nx=temp.x+move[i][0];
int ny=temp.y+move[i][1];
int npath=temp.path+1;
if(check(nx,ny)) {
q.offer(new node(nx,ny,npath));
}
}
}
}
public static boolean check(int x,int y) {
if(x>=0&&y>=0&&x<n&&y<m&&map[x][y]=='.')
return true;
else return false;
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
m=sc.nextInt();
for(int i=0;i<n;i++) {
String s=sc.next();
map[i]=s.toCharArray();
}
bfs(0,0);
System.out.println(sp);
}
}
熟練掌握了BFS模板並且會做簡單題之後,我們來做一下下面這道難度較大的BFS的題目,其實可以可以看出來,難題一般都是在經典例題的基礎上改編的
習題3:走字符迷宮帶鑰匙-bfs
每個元素的值的含義如下 0-牆,1-路,2-起始位置,3-迷宮的出口,
大寫字母-門,小寫字母-對應大寫字母所代表的門的鑰匙
輸入描述:
迷宮的地圖,用二維矩陣表示。第一行是表示矩陣的行數和列數M和N
後面的M行是矩陣的數據,每一行對應與矩陣的一行(中間沒有空格)。M和N都不超過100,
門不超過10扇(A到J,可有多道同一編號的門)。
輸出描述:
最短路徑的長度,是一個整數
示例1:
輸入
5 5
02111
01a0A
01003
01001
01111
輸出
7
解釋:
1、不經過A,只經過1,則需要9步
2、經過a取鑰匙再經過A,則只需要7步
示例2:
輸入
6 6
j21111
1b10A0
11a011
111011
JB0011
111113
輸出
9
解釋:
1、經過a再經過A,需要13步
2、經過b再經過B,需要9步
3、經過j再經過J,需要11步
示例3:
輸入
6 6
j21b11
1110A0
11a011
111011
JB0011
111113
輸出
11
解釋:
1、經過a再經過A,需要13步
2、經過b再經過B,需要13步
3、經過j再經過J,需要11步
示例4:
輸入
6 6
b2fgbb
bae0B0
afaBij
cde0cd
0B0Beb
1bj1b3
輸出
9
/**
題解:
每一步的狀態包含當前座標、路徑長度、【已有門的鑰匙】。
其中已有門的鑰匙就兩種情況:有或沒有,那麼不妨用二進制表示是否有。
keys=0(D,十進制)=0(B,二進制),表示一把鑰匙都沒有
keys=4(D)=100(B),表示有C門的鑰匙
keys=5(D)=101(B),表示有A、C門的鑰匙
keys=20(D)=10100(B),表示有C、E門的鑰匙
v[1][1][0]表示一把鑰匙都沒有的時候經過(1,1)
v[1][1][4]表示只有C門鑰匙的時候經過(1,1)
v[1][1][5]表示有A、C門鑰匙的時候經過(1,1)
v[2][2][20]表示有C、E門鑰匙的時候經過(2,2)
&:按位與 |:按位或 ^:按位異或 <<:按位左移 >>:按位右移
(keys&(1<<(‘D’-‘A’))==0表示keys中沒有D的鑰匙。
(keys&(1<<(ch-‘A’))==0表示keys中沒有ch的鑰匙。
根據隊列先進先出的原理,排在隊首的路徑長度一定最小。
最後進行剪枝。
本題中,一個位置可以走多次,
但是同樣的keys狀態時如果經過多次同一個點,肯定之後走的時候的累計長度會大於第一次走。
所以不能在同樣的keys狀態時,經過同一個點,
即每走一步,都要令vis[x][y][keys]==1,把該狀態剪枝掉。
*/
import java.util.*;
public class 走字符迷宮帶鑰匙bfs {
static class node{
int x,y,path;
int keys; //keys用於記錄鑰匙情況
node(int x,int y,int path,int keys){
this.x=x;
this.y=y;
this.path=path;
this.keys=keys;
}
}
static int MAXV=999;
static int m,n,sp=0;
static char[][] map=new char[MAXV][MAXV];
static int move[][]= {{1,0},{-1,0},{0,1},{0,-1}};
static int[][][] v=new int[101][101][5000];
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
m=sc.nextInt();
n=sc.nextInt();
int x=-1,y=-1;
for(int i=0;i<m;i++) {
String s=sc.next();
map[i]=s.toCharArray();
for(int j=0;j<n;j++) {
if(map[i][j]=='2') {
x=i;y=j;
}
}
}
bfs(x,y);
System.out.println(sp);
sc.close();
}
public static void bfs(int x,int y) {
Queue<node> q=new LinkedList<node>();
q.offer(new node(x,y,0,0));
while(!q.isEmpty()) {
node temp=q.poll();
//v[x][y][0] 表示一把鑰匙都沒有的狀態下,經過了(x,y)點
v[temp.x][temp.y][0]=1;
//也可以先移動,後判斷,如下所示
for(int i=0;i<4;i++) {
int nx=temp.x+move[i][0];
int ny=temp.y+move[i][1];
int npath=temp.path+1;
int keys=temp.keys;
if(check(nx,ny)) {
char ch=map[nx][ny];
if(ch=='3') {
sp=npath;
return ;
}
//碰到了門,卻沒有該門的鑰匙,則不把該點加入隊列,直接走下一步
if(ch>='A'&&ch<='J'&&(keys&(1<<ch-'A'))==0) {
continue;
}
//嘗試獲取鑰匙key,如果是‘a’-‘j’,則之後鑰匙加入keys中,如果是‘1’則key仍爲0
int key=0;
if(ch>='a'&&ch<='j') {
key=1<<(map[nx][ny]-'a');
}
int nkeys=keys|key;
//假設keys=1001(B),key==10(B),則keys|key=1011,
//v[nx][ny][1011]就表示有A、B、D三把鑰匙的時候經過(nx,ny)
if(v[nx][ny][nkeys]==0) {
v[nx][ny][nkeys]=1;
q.offer(new node(nx,ny,npath,nkeys));
}
}
}
}
}
public static boolean check(int x,int y) {
if(x>=0&&y>=0&&x<m&&y<n&&map[x][y]!='0') {
return true;
}else return false;
}
}