✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨
✨ 个人主页:余辉zmh–CSDN博客
✨ 文章所属专栏:搜索回溯算法篇–CSDN博客
一.什么是多源BFS
多源BFS(
Breadth-First Search
,广度优先搜索)是BFS的一种扩展形式,用于从多个起点同时开始搜索。与传统的BFS只能从一个起点出发不同,多源BFS允许从多个起点同时进行搜索,适用于需要从多个位置同时探索的场景。在我的上一篇文章中,用关于使用BFS解决最短路问题的讲解,在这类问题中其实就可以理解为单源BFS搜索,也就是只从一个起始位置开始搜索,而本篇文章则是探讨多个起始位置的最短路问题,使用多源BFS来解。因此在学习这个模块时建议先把单源BFS解决最短路问题理解清楚,才能更好的学习多源BFS。
1.多源BFS的特点
-
多个起点:
在初始化队列时,将所有起点同时加入到队列中。
-
同步扩展:
从所有起点同时开始扩展,逐步向外扩展。
-
层次遍历:
与单源BFS一样,按照层次遍历,确保最先找到的路径是最短的。
2.实现步骤
-
1.初始化队列:
将所有起始节点加入到队列中。
-
2.标记访问:
标记所有起点为已访问。
-
3.层次遍历:
从队列中依次取出节点,扩展其相邻节点,如果未访问则加入到队列中。
-
4.循环步骤3:
直到队列为空或者找到目标。
和单源BFS的实现步骤除了1不同,需要将多个起始节点入队外,其他步骤完全相同。
3.应用场景
-
多目标最短路径:
当需要从多个起点找到某个目标的最短路径时。
-
地图填充:
如floodfill算法,从多个点同时开始填充。
-
多机器人路径规划:
多个机器人同时从不同起点出发,寻找目标或者覆盖区域。
以最短路径为例讲解,如图所示:
二.例题
1.01矩阵
题目:
算法原理:
首先先明白本道题的题意是什么,给定一个原始数组,数组中只有0和1两种数字,将所有值为1的位置修改成离当前位置最近的那个0的距离,也就是从1搜索找到离他最近的0,但是仔细分析就会发现,如果从当前的1位置出发可能会遇到好几个0的位置,如何判断哪一个是最近的0呢?
这里就要使用另一个思想,正难则反思想,如果从1去找最近的0不好找,可以反着来,从0去找1,将0所有可以搜索的1修改成对应的距离,因为原始数组中可能存在多个0的位置,这里就要用到多源BFS搜索,多个0同时向外扩展搜索1,相邻位置的值是当前位置的值+1。
代码实现:
typedef pair<int, int> PII;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
vector<vector<int>> updateMatrix(vector<vector<int>>& mat){
int m = mat.size(), n = mat[0].size();
//建立一个新的二维数组作为结果返回
vector<vector<int>> ret(m, vector<int>(n));
//创建一个队列
queue<PII> q;
//找到原始数组中的所有值为0的位置,并在新的数组中修改,同时入队
for (int i = 0; i < m; i++){
for (int j = 0; j < n; j++){
if(mat[i][j]==0){
ret[i][j] = 0;
q.push({i, j});
}
//将原始数组中非0的位置修改成-1
else{
ret[i][j] = -1;
}
}
}
while(!q.empty()){
auto [a, b] = q.front();
q.pop();
for (int k = 0; k < 4; k++){
int x = a + dx[k], y = b + dy[k];
if(x>=0&&x<m&&y>=0&&y<n&&ret[x][y]==-1){
//将相邻位置值为-1的入队,并修改成当前位置的值+1
q.push({x, y});
ret[x][y] = ret[a][b] + 1;
}
}
}
return ret;
}
2.地图中的最高点
题目:
算法原理:
首先理解题意,原始数组中,1表示水域,0表示陆地
返回一个新的数组,每个位置都表示高度,其中水域高度是0,其他相邻位置的高度与当前位置地高度插不超过1
既然要找某个位置的最大高度,利用贪心地思想,每次相邻位置都比当前位置高1
本道题就转换成01矩阵,多源BFS搜索,从所有水域0的位置开始,逐层的向外扩展搜索,相邻位置的值是当前位置的值+1。
代码实现:
typedef pair<int, int> PII;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
vector<vector<int>> highestPeak(vector<vector<int>>& isWater){
int m = isWater.size(), n = isWater[0].size();
//创建一个新的数组
vector<vector<int>> ret(m, vector<int>(n));
//创建一个队列
queue<PII> q;
//遍历原始数组,将值为1表示水域的下标入队,同时对应到新数组修改成高度0
//值为0的表示陆地的下标,对应到新数组中修改成-1,
for (int i = 0; i < m; i++){
for (int j = 0; j < n; j++){
if(isWater[i][j]==1){
q.push({i, j});
ret[i][j] = 0;
}
else{
ret[i][j] = -1;
}
}
}
//循环出队
while(!q.empty()){
//获取队头下标,并出队
auto [a,b]=q.front();
q.pop();
for (int k = 0; k < 4; k++){
int x = a + dx[k], y = b + dy[k];
if(x>=0&&x<m&&y>=0&&y<n&&ret[x][y]==-1){
ret[x][y] = ret[a][b] + 1;
q.push({x, y});
}
}
}
return ret;
}
3.地图分析
题目:
算法原理:
本道题的思路还是和01矩阵一样,找到距离陆地最远的海洋格,还是正难则反思想,反过来找,从陆地找海洋格,多源BFS搜索,逐层向外扩展。
代码实现:
typedef pair<int, int> PII;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int maxDistance(vector<vector<int>>& grid){
//还是和01矩阵一样,正难则反,反过来从陆地找海洋
//原始数组中0表示海洋,1表示陆地
int m = grid.size(), n = grid[0].size();
vector<vector<int>> ret(m, vector<int>(n));
queue<PII> q;
for (int i = 0; i < m; i++){
for (int j = 0; j < n; j++){
if(grid[i][j]==1){
q.push({i,j});
ret[i][j] = 0;
}
else{
ret[i][j] = -1;
}
}
}
if(q.size()==0||q.size()==m*n){
return -1;
}
int maxnum = 0;
while(!q.empty()){
//获取队头下标,并出队
auto [a,b]=q.front();
q.pop();
for (int k = 0; k < 4; k++){
int x = a + dx[k], y = b + dy[k];
if(x>=0&&x<m&&y>=0&&y<n&&ret[x][y]==-1){
ret[x][y] = ret[a][b] + 1;
maxnum = max(maxnum, ret[x][y]);
q.push({x, y});
}
}
}
return maxnum;
}
4.飞地的数量
题目:
算法原理:
本道题其实就是上面写的多源BFS应用场景的示例二,利用floodfill算法思想,搜索连通块。
首先来看题意,给定一个二维数组,数组中只有两种值0或者1,而数值为1的有两种连通块,一种是与边界相连,一种是没有相连,全部被0围绕,找到所有没有与边界相连的1,返回总数量。
代码实现:
typedef pair<int, int> PII;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int numEnclaves(vector<vector<int>>& grid){
//正难则反思想,找没有与边界相连的飞地数量难,可以反着来找与边界相连的飞地,剩下的就是最终要找的
//可以使用深度搜索,也可以使用广度搜索,这里使用多源bfs
int m = grid.size(), n = grid[0].size();
//创建一个队列
queue<PII> q;
//遍历原始数组将边界上所有的1作为起始位置入队,并修改成2
//第一列和最后一列
for (int i = 0; i < m; i++){
if(grid[i][0]==1){
grid[i][0] = 2;
q.push({i, 0});
}
if(grid[i][n-1]==1){
grid[i][n - 1] = 2;
q.push({i, n - 1});
}
}
//第一行和最后一行
for (int j = 0; j < n; j++){
if(grid[0][j]==1){
grid[0][j] = 2;
q.push({0, j});
}
if(grid[m-1][j]==1){
grid[m - 1][j] = 2;
q.push({m - 1, j});
}
}
while(!q.empty()){
//获取队头元素
auto [a, b] = q.front();
q.pop();
for (int k = 0; k < 4; k++){
int x = a + dx[k], y = b + dy[k];
if(x>=0&&x<m&&y>=0&&y<n&&grid[x][y]==1){
//将所有与边界相连的1都修改成2
grid[x][y] = 2;
//入队
q.push({x, y});
}
}
}
//遍历整个数组,遇到2说明是与边界相连的,遇到1说明是剩下的飞地,个数相加即可
int ret=0;
for(auto nums : grid){
for(auto num : nums){
if(num==1){
ret++;
}
}
}
return ret;
}
以上就是关于多源BFS练习题的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!