蓝桥杯——一篇搞懂广度优先搜索(BFS)

大家好,我是璐画


博客主页:璐画的个人主页
欢迎关注点赞收藏⭐️留言
本文由璐画原创,CSDN首发!
首发时间:2022年3月21日
✉️坚持下去的,都是因为热爱吧!
参考书籍:《labuladong的算法小抄》
参考在线编程网站:牛客网力扣
作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!

特别声明:本文我是学习过labuladong大佬的书然后总结出来的文章,这位大佬在csdn上好像没有账号,想学习的小伙伴可以买大佬那本《labuladong的算法小抄》,写的很好

 目录

目录

 目录

核心框架

例题

题1求二叉树的最短路径

题1代码

题二 最短路径问题

练习

总结

送给大家一句话


 

 

核心框架

BFS 的核心思想应该不难理解的,就是把一些问题抽象成图,从一个点开始,向四周开始扩散。一般来说,我们写 BFS 算法都是用「队列」这种数据结构,每次将一个节点周围的所有节点加入队列。

BFS 相对 DFS 的最主要的区别是:BFS 找到的路径一定是最短的,但代价就是空间复杂度比 DFS 大很多

我们先举例一下 BFS 出现的常见场景好吧,问题的本质就是让你在一幅「图」中找到从起点start到终点target的最近距离,这个例子听起来很枯燥,但是 BFS 算法问题其实都是在干这个事儿。把枯燥的本质搞清楚了,再去欣赏各种问题的包装才能胸有成竹嘛。这个广义的描述可以有各种变体,比如走迷宫,有的格子是围墙不能走,从起点到终点的最短距离是多少?如果这个迷宫带「传送门」可以瞬间传送呢?

再比如说两个单词,要求你通过某些替换,把其中一个变成另一个,每次只能替换一个字符,最少要替换几次?

再比如说连连看游戏,两个方块消除的条件不仅仅是图案相同,还得保证两个方块之间的最短连线不能多于两个拐点。你玩连连看,点击两个坐标,游戏是如何判断它俩的最短连线有几个拐点的?

再比如……

净整些花里胡哨的,这些问题都没啥奇技淫巧,本质上就是一幅「图」,让你从一个起点,走到终点,问最短路径。这就是 BFS 的本质,框架搞清楚了直接默写就好。

上核心框架

// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
    Queue q; // 核心数据结构
    Set visited; // 避免走回头路

    q.offer(start); // 将起点加入队列
    visited.add(start);
    int step = 0; // 记录扩散的步数

    while (q not empty) {
        int sz = q.size();
        /* 将当前队列中的所有节点向四周扩散 */
        for (int i = 0; i < sz; i++) {
            Node cur = q.poll();
            /* 划重点:这里判断是否到达终点 */
            if (cur is target)
                return step;
            /* 将 cur 的相邻节点加入队列 */
            for (Node x : cur.adj())
                if (x not in visited) {
                    q.offer(x);
                    visited.add(x);
                }
        }
        /* 划重点:更新步数在这里 */
        step++;
    }
}

队列q就不说了,BFS 的核心数据结构;cur.adj()泛指cur相邻的节点,比如说二维数组中,cur上下左右四面的位置就是相邻节点;visited的主要作用是防止走回头路,大部分时候都是必须的,但是像一般的二叉树结构,没有子节点到父节点的指针,不会走回头路就不需要visited。

例题

题1求二叉树的最短路径

蓝桥杯——一篇搞懂广度优先搜索(BFS)_第1张图片

这是力扣上的移到简单题,现在大家暂停一分钟,思考这道题,最好直接套上上面的核心框架

题1代码

int minDepth(TreeNode root) {
    if (root == null) return 0;
    Queue q = new LinkedList<>();
    q.offer(root);
    // root 本身就是一层,depth 初始化为 1
    int depth = 1;

    while (!q.isEmpty()) {
        int sz = q.size();
        /* 将当前队列中的所有节点向四周扩散 */
        for (int i = 0; i < sz; i++) {
            TreeNode cur = q.poll();
            /* 判断是否到达终点 */
            if (cur.left == null && cur.right == null) 
                return depth;
            /* 将 cur 的相邻节点加入队列 */
            if (cur.left != null)
                q.offer(cur.left);
            if (cur.right != null) 
                q.offer(cur.right);
        }
        /* 这里增加步数 */
        depth++;
    }
    return depth;
}

如果没接触过bfs的同学,到这里可能就有点迷糊了,这个到底是什么意思?

不要着急,看这道题,在对比这上面的框架看,你有没有发现什么,没错!几乎一模一样

这里我给大家写下框架套路,方便大家的理解

1、创建bfs核心队列,创建一个辅助数组来记录该节点是否被访问过

2、添加首节点入队,初始化步数(目前只针对题1)

3、while循环,循环条件是队列不为空

4、出队,判断出队的节点是否满足边界(就是结束条件),如果满足就return

5、扩展,将这个节点周围需要扩展的节点入队

6、步数++;

 这大概就是这道题的思路,套上框架,你会发现几乎一模一样吧,如果这道题还不明显,下面来一道蓝桥杯真题,最短路径

题二 最短路径问题

//给定一个 N×M 的网格迷宫 G。G 的每个格子要么是道路,要么是障碍物(道路用1 表示,障碍物用 0 表示)。
//已知迷宫的入口位置为 (x1,y1),出口位置为 (x2,y2)问从入口走到出口,最少要走多少个格子。
//输入第 1 行包含两个正整数 N,M,分别表示迷宫的大小。
//接下来输入一个 N×M的矩阵。若 G{i,j}=1 表示其为道路,否则表示其为障碍物。
//最后一行输入四个整数 x1,y1,x2,y2,表示入口的位置和出口的位置。

 首先定义一些固定的内容

static int[][] arr;//存储地图的二维数组
static int[][] bool;//辅助数组,访问过为1,没访问过为0
static int[][] dir = {{1,0},{-1,0},{0,1},{0,-1}};//上下左右
//在bfs中,一般会借助结点去解题
public static class Node{
		int x,y;//坐标
		int stmp;//变量
}
//地图的行 列
static int row,col;

 bfs代码

public static int dfs() {
		Queue queue = new LinkedList<>();//创建队列
		queue.offer(start);//将首结点入队
		while(!queue.isEmpty()) {//判断队列是否空
			Node top = queue.poll();//出队
			if(top.x==end.x&&top.y==end.y) {//判断是否满足边界,满足边界就返回
				return top.stmp;
			}
			for(int i=0;i<4;i++) {//该结点的扩展,上下左右,根据题意来
				int dx = top.x+dir[i][0];//扩展后的x坐标
				int dy = top.y+dir[i][1];//扩展后的y坐标
				if(test(dx,dy)) {//判断扩展后是否满足条件
					Node node = new Node();
					node.x = dx;
					node.y = dy;
					node.stmp = top.stmp+1;
					queue.offer(node);//将新结点入队
					bool[dx][dy] = 1;//标记该结点已经访问过
				}
			}
		}
		return -1;
	}

好了,到这里懂了一些了吧?下面再练几道例题,相信自己尝试着独立思考,一定可以掌握bfs

练习

题目描述
小明有一块空地,他将这块空地划分为 n 行 m 列的小块,每行和每列的长度都为 1。
小明选了其中的一些小块空地,种上了草,其他小块仍然保持是空地。
这些草长得很快,每个月,草都会向外长出一些,如果一个小块种了草,则它将向自己的上、下、左、右四小块空地扩展,
这四小块空地都将变为有草的小块。请告诉小明,k 个月后空地上哪些地方有草。
输入描述
输入的第一行包含两个整数 n, m。
接下来 n 行,每行包含 m 个字母,表示初始的空地状态,字母之间没有空格。如果为小数点,表示为空地,如果字母为 g,表示种了草。
接下来包含一个整数 k。 其中 2≤n,m≤1000,1≤k≤1000。
输出描述
输出 n 行,每行包含 m 个字母,表示 k 个月后空地的状态。如果为小数点,表示为空地,如果字母为 g,表示长了草。
示例
输入
4 5
.g…

…g…

2
输出
gggg.
gggg.
ggggg
.ggg.

 代码

//这道题就是典型的广搜问题,同时向四周扩散,可以说深搜是一条线 那广搜是一个面,再简单点说,深搜是个体出动,广
搜就是群体出动了
static char[][] chars;
static int[][] bool;
static int[][] dir = {{1,0},{-1,0},{0,1},{0,-1}};
static int row,col;
static int k;
public static Node{
	int x,y;
	int stmp;
	public Node(int x,int y,int stmp){
		this.x = x;
		this.y = y;
		this.stmp = stmp;
	}
}
public static void main(String[] args){
	Scanner sc = new Scanner(System.in);
	row = sc.nextInt();
	col = sc.nextInt();
	chars = new char[row][col];
	bool = new int[row][col];
	for(int i=0;i queue = new LinkedList<>();
	for(int i=0;i=row||dy<0||dy>=col){
				continue;
			}
			if(bool[dx][dy]==1){
				continue;
			}
			chars[dx][dy] = 'g';
			bool[dx][dy] = 1;
			queue.offer(new Node(dx,dy,nstmp));
		}
	}
}

 

下图给出了一个迷宫的平面图,其中标记为 1 的为障碍,标记为 0 的为可以通行的地方。
010000
000100
001001
110000
迷宫的入口为左上角,出口为右下角,在迷宫中,只能从一个位置走到这 个它的上、下、左、右四个方向之一。

对于上面的迷宫,从入口开始,可以按 DRRURRDDDR 的顺序通过迷宫, 一共 10 步。其中 D、U、L、R分别表示向下、向上、向左、向右走。 对于下面这个更复杂的迷宫(30 行 50 列),请找出一种通过迷宫的方式,其使用的步数最少,在步数最少的前提下,请找出字典序最小的一个作为答案。
请注意在字典序中 D 01010101001011001001010110010110100100001000101010
00001000100000101010010000100000001001100110100101
01111011010010001000001101001011100011000000010000
01000000001010100011010000101000001010101011001011
00011111000000101000010010100010100000101100000000
11001000110101000010101100011010011010101011110111
00011011010101001001001010000001000101001110000000
10100000101000100110101010111110011000010000111010
00111000001010100001100010000001000101001100001001
11000110100001110010001001010101010101010001101000
00010000100100000101001010101110100010101010000101
11100100101001001000010000010101010100100100010100
00000010000000101011001111010001100000101010100011
10101010011100001000011000010110011110110100001000
10101010100001101010100101000010100000111011101001
10000000101100010000101100101101001011100000000100
10101001000000010100100001000100000100011110101001
00101001010101101001010100011010101101110000110101
11001010000100001100000010100101000001000111000010
00001000110000110101101000000100101001001000011101
10100101000101000000001110110010110101101010100001
00101000010000110101010000100010001001000100010101
10100001000110010001000010101001010101011111010010
00000100101000000110010100101001000001000000000010
11010000001001110111001001000011101001011011101000
00000110100010001000100000001000011101000000110011
10101000101000100010001111100010101001010000001000
10000010100101001010110000000100101010001011101000
00111100001000010000000110111000000001000000001011
10000001100111010111010001000110111010101101111000

 代码

//这道题主要在于最短路和字典序最下的路径
//首先最短路问题,想到用广搜作,然后字典序,题中也给过你了,D29||y<0||y>49) return false;
		if(arr[x][y]==1) return false;
		if(bool[x][y]==1) return false;
		return true;
	}
	public static void bfs(Node start) {
		Queue queue = new LinkedList<>();
		queue.offer(start);
		bool[start.x][start.y] = 1;
		String s = "";
		while(!queue.isEmpty()) {
			Node node = queue.poll();
			if(node.x==29&&node.y==49) {
				s = node.stmp;
				break;
			}
			for(int i=0;i<4;i++) {
				int dx = node.x+dir[i][0];
				int dy = node.y+dir[i][1];
				if(dx>=0&&dx<=29&&dy>=0&&dy<=49&&arr[dx][dy]=='0'&&bool[dx][dy]!=1){
	                queue.offer(new Node(dx, dy, node.stmp+chars[i]));
	                bool[dx][dy]=1;
	            }
			}
		}
		System.out.println(s);
	}
}

总结

以前没接触过bfs的同学,如果看到这里,真的已经很厉害了,而且我相信,各位把例题全部动手敲一遍,对照着模板想一遍,你肯定能掌握大部分了,加油,广搜必拿下!!

 

送给大家一句话

乾坤未定,你我解释黑马!!!

 

 

 

 

 

 

你可能感兴趣的:(蓝桥杯,java,蓝桥杯,bfs)