BFS(廣度優先搜索)求迷宮題的解法(附詳細註釋) - java語言

下面介紹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;
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章