算法系列--多源BFS问题

发布于:2024-05-03 ⋅ 阅读:(25) ⋅ 点赞:(0)

💕"对相爱的人来说,对方的心意,才是最好的房子。"💕
作者:Lvzi
文章主要内容:算法系列–多源BFS问题
在这里插入图片描述

大家好,今天为大家带来的是算法系列--多源BFS问题

前言:
之前我们已经学习过单源的最短路问题,核心就是通过bfs实现来记录最短的路径,解题步骤如下:

  1. 将起点添加进队列–q.add(start)
  2. 一层一层往外扩展(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问题的代码 比较固定