💕"对相爱的人来说,对方的心意,才是最好的房子。"💕
作者:Lvzi
文章主要内容:算法系列–多源BFS问题
大家好,今天为大家带来的是
算法系列--多源BFS问题
前言:
之前我们已经学习过单源的最短路问题
,核心就是通过bfs
实现来记录最短的路径,解题步骤如下:
- 将起点添加进队列–q.add(start)
- 一层一层往外扩展(step++)
所谓的多源最短路问题
就是有多个起点的最短路问题,如何解决呢?相信大家的第一想法是:不就是多个起点么,每个起点我都做一次单源的最短路问题,再返回结果中最小的不就行了么,这种做法当然是可以的,但是时间复杂度过高,下面讲解第二种方法–超源起点
超源起点
就是将所有的起点当做一个起点,以这个点为起点到终点之间的路径就是最短路,从感性上讲为什么这种策略是对的呢?假设我们现在有三个起点,则最短的路径一定是在以这三个点为起点的最短路径之中,则最后求出的一定是最短路径
这样我们就将多源的最短路问题转化为单源的最短路问题!解法同单源最短路问题,下面是leetocode上比较经典的多源bfs
问题
一.01 矩阵(medium)
题目链接:
01 矩阵(medium)
分析:
转化为从0到1的最短路问题
代码:
// 正难则反的思想
// 将0看作起点,1看做终点 转化为从0-1的最短路问题
class Solution {
int[] dx = { 1, -1, 0, 0 };
int[] dy = { 0, 0, 1, -1 };
public int[][] updateMatrix(int[][] mat) {
int m = mat.length, n = mat[0].length;
int[][] dist = new int[m][n];
Queue<int[]> q = new LinkedList<>();
// 将所有起点添加进入队列之中
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (mat[i][j] != 0)
dist[i][j] = -1;// 将不是起点的位置全部初始化为-1
if (mat[i][j] == 0)
q.add(new int[] { i, j });// 将所有的起点添加进队列之中
}
}
// 多源bfs
while (!q.isEmpty()) {
int[] t = q.poll();
int a = t[0], b = t[1];
for (int i = 0; i < 4; i++) {
int x = a + dx[i], y = b + dy[i];
if (x >= 0 && y >= 0 && x < m && y < n && dist[x][y] == -1) {
q.add(new int[] { x, y });
dist[x][y] = dist[a][b] + 1;
}
}
}
return dist;
}
}
总结:
两个小优化的地方:在单源bfs问题之中,我们经常使用三个变量
boolean[][] vis
:用于标记是否被搜索过int step
:用于记录当前的层数(路径长度)int sz
:当前层一共有多少个节点
但是在本题中这三个变量都不存在,实际上是通过其他方式来替代了:
将dist数组中不是起点的位置初始化为-1
–替代了vis的作用,在判断时,只需判断dist[x][y]是否等于-1即可,如果等于证明没有被搜索过- 由于我们在往外扩展的时候是一步一步往外扩展的,dist[a][b]存储的就是走到当前层所需的最小步数,那么以
a,b
为起点的下一层的所有位置的步数全部都是dist[a][b] + 1
的值,不需要通过step来记录路径长度,且也不需要sz来保证是同一层(只要dist[a][b]相同,就是同一层)
二.⻜地的数量
题目链接:⻜地的数量
分析:
和被围绕的区域类似,这里用的是多源bfs
代码:
// 1.将边界上所有的1添加进入队列
// 2.将所有的联通块(1)标记为0
// 3.统计1的数目,返回即可
class Solution {
int[] dx = {1,-1,0,0};
int[] dy = {0,0,1,-1};
boolean[][] vis;
public int numEnclaves(int[][] grid) {
int m = grid.length, n = grid[0].length;
vis = new boolean[m][n];
Queue<int[]> q = new LinkedList<>();
// 将边界上所有的1添加进入队列
for(int i = 0; i < m; i++) {
if(grid[i][0] == 1) q.add(new int[]{i,0});
if(grid[i][n - 1] == 1) q.add(new int[]{i,n - 1});
}
for(int j = 0; j < n; j++) {
if(grid[0][j] == 1) q.add(new int[]{0,j});
if(grid[m - 1][j] == 1) q.add(new int[]{m - 1, j});
}
// 多源bfs 将所有的联通块1标记为0
while(!q.isEmpty()) {
int[] t = q.poll();
int a = t[0], b = t[1];
grid[a][b] = 0;
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) {
q.add(new int[]{x, y});
grid[x][y] = 0;
}
}
}
int ret = 0;
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
if(grid[i][j] == 1) ret++;
return ret;
}
}
三.地图中的最⾼点
题目链接:地图中的最⾼点
分析:
多源bfs
代码:
// 和矩阵那道题目相同
class Solution {
int[] dx = {1,-1,0,0};
int[] dy = {0,0,1,-1};
public int[][] highestPeak(int[][] isWater) {
int m = isWater.length, n = isWater[0].length;
int[][] height = new int[m][n];
Queue<int[]> q = new LinkedList<>();
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(isWater[i][j] == 1) {
q.add(new int[]{i, j});// 将水域添加进队列
}else {
height[i][j] = - 1;// 陆地置为-1
}
}
}
// 多源bfs
while(!q.isEmpty()) {
int[] t = q.poll();
int a = t[0], b = t[1];
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 && height[x][y] == -1) {
q.add(new int[]{x, y});
height[x][y] = height[a][b] + 1;
}
}
}
return height;
}
}
四.地图分析
题目链接:地图分析
分析:
多源bfs + 更新最值
以海洋为起点计算到陆地的最短距离比较困难(无法确定是哪一个海洋)
所以改为以陆地为起点,计算其到海洋的距离
代码:
class Solution {
int[] dx = {1,-1,0,0};
int[] dy = {0,0,1,-1};
public int maxDistance(int[][] grid) {
int n = grid.length, ret = 0;
int[][] dist = new int[n][n];
Queue<int[]> q = new LinkedList<>();
boolean flg = false;
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
if(grid[i][j] == 1) q.add(new int[]{i, j});// 将陆地当做起点
else {
dist[i][j] = -1;// 将所有海洋置为-1
flg = true;// 存在海洋
}
}
}
if(q.size() == 0) return -1;// 全是海洋
if(!flg) return -1;// 全是陆地
// 多源bfs
while(!q.isEmpty()) {
int[] t = q.poll();
int a = t[0], b = t[1];
for(int k = 0; k < 4; k++) {
int x = a + dx[k], y = b + dy[k];
if(x >= 0 && y >= 0 && x < n && y < n && dist[x][y] == -1) {
q.add(new int[]{x, y});
dist[x][y] = dist[a][b] + 1;
ret = Math.max(ret, dist[x][y]);// 更新最值
}
}
}
return ret;
}
}
五.总结
- 多源bfs最常用的一个思想就是
正难则反
,这个思想主要用于解决谁是起点,谁是终点
的问题 - 多源bfs问题的代码 比较固定