【重难点算法】多源 BFS

单源与多源 BFS

单源 BFS 是从图中的某一点(源点)出发,向周围进行广度优先搜索,在搜索的过程中记录每一点到源点的距离。多源 BFS 是从多个源点同时开始进行广度优先搜索,通常需要记录在搜索过程中当前点距离最近的源点的距离。广度优先搜索是用队列来实现的,对于单源的问题,队列的初始化就是某一个单一源点;对于多源问题,队列的初始化就是多个源点。这也是单源和多源问题的唯一区别。

实例剖析

例题1 矩阵距离

矩阵距离

给定一个 01 矩阵,返回一个相同大小的矩阵 d i s dis dis,其中 d i s [ i ] [ j ] dis[i][j] dis[i][j] 表示 ( i , j ) (i,j) (i,j) 位置距离最近的 1 的曼哈顿距离。

【分析】对于这样的一个 01 矩阵,我们选择从所有的 1 出发,通过广度优先搜索的方法来计算 ( i , j ) (i,j) (i,j) 到最近的 1 的距离。在本问题中,如果 1 的个数仅有一个,那么就是单源 BFS 问题,否则就是多源 BFS 问题。

  • 首先,我们定义一个答案数组用来记录到最近的 1 的距离,数组初始化为 -1;定义一个队列,用来存放矩阵中 1 的位置。
  • 接着遍历矩阵,将矩阵中 1 的位置存放在队列中,并将当前位置的 dis 赋值为 1,毕竟 1 距离最近的 1 距离为 0 嘛。
  • 然后从队列中的 1 的位置 ( i , j ) (i,j) (i,j) 一层层的向外扩展到新的位置 ( x , y ) (x,y) (x,y)如果新的位置之前没有到达过,那么更新 d i s [ x ] [ y ] = d i s [ i ] [ j ] + 1 dis[x][y] = dis[i][j] + 1 dis[x][y]=dis[i][j]+1,并将扩展到的位置 ( x , y ) (x,y) (x,y) 作为新的 “1” 存入队列。重复本步骤,直至队列为空。
  • 不知道大家是否会有这样的疑问:为什么上述步骤中的 ( x , y ) (x,y) (x,y) 距离最近的 1 的距离 d i s [ x ] [ y ] dis[x][y] dis[x][y] 有这样的等式?因为,“如果新的位置之前没有到达过” 这句话表名从其他 1 的位置出发还未到达这个新的位置 ( x , y ) (x,y) (x,y),而从当前的 1 广搜到达了这个新的位置,所以有 d i s [ x ] [ y ] = d i s [ i ] [ j ] + 1 dis[x][y] = dis[i][j] + 1 dis[x][y]=dis[i][j]+1 这样的计算 ( x , y ) (x,y) (x,y) 到最近的 1 距离的计算公式。

【示例代码】

class Solution{
public:
    const int dirs[4][2] = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
    
    vector<vector<int>> getNearest1Distance(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        vector<vector<int>> dis(m, vector<int>(n, -1));
        quequ<pair<int, int>> que;
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == 1) {
                    que.push({i, j});
                    dis[i][j] = 0;
                }
            }
        }
        
        while (!que.empty()) {
            auto pr = que.front();
            que.pop();
            int i = pr.first, j = pr.second;
            for (int k = 0; k < 4; ++k) {
                int x = i + dirs[k][0];
                int y = j + dirs[k][1];
                if (x >= 0 && x < m && y >= 0 && y < n && dis[x][y] == -1) {
					dis[x][y] = dis[i][j] + 1;
                    que.push(make_pair(x, y));
                }
            }
        }
        
     	return dis;   
    }
};

例题2 地图分析

1162. 地图分析

你现在手里有一份大小为 n x n 的 网格 grid,上面的每个 单元格 都用 01 标记好了。其中 0 代表海洋,1 代表陆地。

请你找出一个海洋单元格,这个海洋单元格到离它最近的陆地单元格的距离是最大的,并返回该距离。如果网格上只有陆地或者海洋,请返回 -1

我们这里说的距离是「曼哈顿距离」( Manhattan Distance):(x0, y0)(x1, y1) 这两个单元格之间的距离是 |x0 - x1| + |y0 - y1|

【分析】本题就是统计 ( i , j ) (i,j) (i,j) 到最近的 1 的距离,如果记录矩阵中 1 的位置的队列的大小为空或者为 n ∗ n n*n nn,则说明矩阵中全为 0 或者全为 1,直接返回 -1。

【示例代码】

class Solution {
public:
    const int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    int maxDistance(vector<vector<int>>& grid) {
        int n = grid.size();
        vector<vector<int>> dis(n, vector<int>(n, -1));

        queue<pair<int, int>> que;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == 1) {
                    que.push({i, j});
                    dis[i][j] = 0;
                }
            }
        }

        if (que.empty() || que.size() == n*n) {
            return -1;
        }

        int res = 0;
        while (!que.empty()) {
            auto pr = que.front();        // 避免 auto& [i, j] 的写法
            int i = pr.first, j = pr.second;
            que.pop();

            for (int k = 0; k < 4; ++k) {
                int x = i + dirs[k][0];
                int y = j + dirs[k][1];

                if (x >= 0 && x < n && y >= 0 && y < n && dis[x][y] == -1) {
                    dis[x][y] = dis[i][j] + 1;
                    que.push({x, y});
                    res = max(res, dis[x][y]);
                }
            }
        }
        return res;
    }
};

例题3 腐烂的橘子

994. 腐烂的橘子

在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:

  • 0 代表空单元格;
  • 1 代表新鲜橘子;
  • 2 代表腐烂的橘子。

每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。

返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1

【示例】

输入:grid = [[2,1,1],[1,1,0],[0,1,1]]
输出:4

在上述示例中,仅有一个烂橘子,那么该示例对应的就是单源 BFS 问题。一般情况下,该题是一个多源BFS问题。格子里有三种值分别代表不同的情况:

  • 0 表示空格子;
  • 1 表示格子中有新鲜的橘子;
  • 2 表示格子中是烂橘子。

烂橘子每一秒会污染周围 4 方向上的新鲜橘子,并让其腐烂。我们要计算的是什么时候单元格中没有新鲜橘子了。

典型的多源广度优先搜索问题,我们从每一个烂橘子的单元格子出发,一圈一圈的污染周围四方向上的新鲜橘子,直到新鲜橘子的数量为 0。多源是多个源头的意思,这里代表的是多个烂橘子。使用广度优先算法解题,具体过程为:

  • 首先遍历网格,使用队列记录存储烂橘子的位置坐标并统计新鲜橘子的数量 c n t cnt cnt
  • 接着从烂橘子的位置坐标出发,每次向外扩一圈到新位置 ( n x , n y ) (nx, ny) (nx,ny),并更新新位置到最近的烂橘子的距离 d i s t [ n x ] [ n y ] dist[nx][ny] dist[nx][ny]。因为每次向外扩一个单位距离,所以有 dist[nx][ny] = dist[x][y] + 1
  • 在向外扩展的过程中,如果 dist[nx][ny] = 1,即表明当前遇到了新鲜橘子,就将新鲜橘子数量减一,并更新 $ans = dis[nx][ny] $,如果 c n t = 0 cnt = 0 cnt=0 则说明新鲜橘子已经全部被污染了,可以提前退出 w h i l e while while 循环。
  • 如果搜索结束后 c n t > 0 cnt > 0 cnt>0,说明有新鲜句子没被污染腐烂,返回 -1。

【示例代码】

class Solution {
    int cnt;        // 统计新鲜橘子的数量
    int dist[10][10];
    const int dirs[4][2] = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};

public:
    int orangesRotting(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size(), ans = 0;
        memset(dist, -1, sizeof(dist));
        cnt = 0;

        queue<pair<int, int>> que;
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == 2) {
                    que.push({i, j});
                    dist[i][j] = 0;
                }
                else if (grid[i][j] == 1) {
                    ++cnt;
                }
            }
        }

        while (!que.empty()) {
            pair<int, int> x = que.front();
            que.pop();
            for (int i = 0; i < 4; ++i) {
                int nx = x.first + dirs[i][0];
                int ny = x.second + dirs[i][1];
                // ~ 表示的是取反操作,-1取反是0,~dist[nx][ny] 如果 >= 0 表示已经访问过这个方格
                if (nx < 0 || nx >= m || ny < 0 || ny >= n || grid[nx][ny] == 0 || dist[nx][ny] > -1) {
                    continue;
                }
                dist[nx][ny] = dist[x.first][x.second] + 1;
                que.push(make_pair(nx, ny));
                if (grid[nx][ny] == 1) {
                    --cnt;
                    ans = dist[nx][ny];
                    if (cnt == 0) {
                        break;
                    }
                }
            }
        }

        return cnt ? -1: ans;
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wang_nn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值