BFS(广度优先搜索)刷题模板(附详细注释)及经典例题 - java语言

BFS的题目一般不给出邻接矩阵,只给出迷宫图
BFS一般用于求图中起点到终点的最短路径sp
下面以经典例题为例,给大家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 path;	//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;		 				      //大小为n*m的地图
	static int[][] map2=new int[MAXV][MAXV];  //二选一,输入全为数字的地图
	static char[][] map=new char[MAXV][MAXV];  //二选一,输入含有字符的地图
	static int[][] v=new int[MAXV][MAXV];	  //判定该点是否被访问过,0为未访问,1为访问过
    static int  sp=-1;   					  //最终结果shortest path,用于存最短路径的长度
  //move数组的元素顺序,会决定遍历时的顺序,本例中顺序为“上下左右”
	static int[][] move= {{0,1},{0,-1},{1,0},{-1,0}};  
	
	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算法
	public static void bfs(int x,int y) {  
      //如果有需要,也可以把queue放到方法外面,加上static,作为类变量
		Queue<node> q=new LinkedList<node>();
		q.add(new node(x,y,0));
		
		while(!q.isEmpty()) {
			
			//1、进来之后,要做什么
			node temp=q.poll();
			v[temp.x][temp.y]=1;
			
			//2、如果是终点,要做什么
			if(map[temp.x][temp.y]=='T') {
				sp=temp.path;
				return ;
			}
			
			//3、如果不是终点,要做什么
			for(int i=0;i<4;i++) {
				
				//不能直接改变原结点中的x,y,path的值,要另设变量
				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) {
		//注意是map[x][y]!='#',如果改成map[x][y]=='.',会忽略‘T’的情况
		if(x>=0&&y>=0&&x<n&&y<m&&v[x][y]==0&&map[x][y]!='#')
			return true;
		else return false;
	}
}

得到了BFS的模板之后,再来练习一下下面这道字符迷宫的例题

习题2:走字符迷宫2-bfs

描述
一个迷宫由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 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);
	}
	
	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;
	}
}

熟练掌握了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;
	}
}

发布了4 篇原创文章 · 获赞 10 · 访问量 1105

猜你喜欢

转载自blog.csdn.net/qq_32022299/article/details/104356469