多源 BFS 详解

目录

一、多源与单源的区别

二、例题练习

2.1 例题1:01 矩阵

2.2 例题2:飞地的数量

2.3 例题3:地图中的最高点 

2.4 例题4:地图分析


一、多源与单源的区别

单源最短路问题如何解决已经在上篇博客给出BFS 解决最短路问题,如果还没学习单源的建议先学习,因为多源就是在单源的基础上进行一些改动即可,即把多个源点一起放入队列中。

• 注意:

多源 BFS 只能用来解决边权为 1 (其实只要边权相等即可,可以转化为边权 1 来解决)的多源最短路问题。如果是边权不相等的情况那么就要使用到图论里面的 Floyd-Warshall 算法,这个后续会写到。

那么该如何解决多源 BFS 问题呢?

解法1:暴力,直接把多源最短路问题转化为若干个单源最短路问题,这个大概率是会超时的,因为重复查找的次数非常多。

解法2:把所有的源点当成一个 “超级源点”,那么问题就变成了单一的单源最短路问题。

如何证明正确性呢? 

理性:严格的证明,这些在一些算法书上能够找到,比较麻烦,文章主要写,如何来做题。故我们就感性的理解一下。

感性:就好比如下图这些性能相同的汽车(把汽车看成开始的源点),如果采用单源最短路的解决思路就是一辆一辆车启动,最后比较谁先到终点。如果采用多源最短路的解题思路就是把这些车一起启动,谁先到终点,就是最短路。这里可能有的友友会问,这样和单源最短路的时间复杂度不是一样的嘛?其实当在同一条路上如果有节点在前面,那么后面的节点就可以直接舍去,因为绝对不可能是最短路。

二、例题练习

2.1 例题1:01 矩阵

• 题目链接:01 矩阵

• 问题描述:

给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。

两个相邻元素间的距离为 1 。

• 解题思路:

如果采用单源最短路(一个位置一个位置的去求)的话会超时(替大家试过了),正确的解法多源 BFS + 正难则反(填表更快),把所有的 0 当成起点,1当作终点(终点可以有多个)。找到一个 1 且是没有被遍历过的就可以把路径数直接填入,因为绝对没有比这个更小的路径了。

• 代码编写:

直接把最开始创建的数组都初始化成 -1 ,这样就不用创建一个 vis 表来标记已经走过的节点。且路径数可以直接在原来的路径数上 + 1 即可,一举两得。具体代码如下:

class Solution {
    int[] dx = {0,0,1,-1};
    int[] dy = {1,-1,0,0};
    public int[][] updateMatrix(int[][] mat) {
        //1.把所有为0的元素存入队列
        int n = mat.length;
        int m = mat[0].length;
        //2.创建 dest 数组
        //3.dest初始化为 - 1
        int[][] dest = new int[n][m];
        for(int i = 0;i < n;i++){
            Arrays.fill(dest[i],-1);
        }
        Queue<int[]> q = new LinkedList<>();
        for(int i = 0;i < n;i++){
            for(int j = 0;j < m;j++){
                if(mat[i][j] == 0){
                    dest[i][j] = 0;
                    q.add(new int[]{i,j});
                }
            }
        }
        //4.bfs
        while(!q.isEmpty()){
            int[] tmp = q.poll();
            int a = tmp[0],b = tmp[1];
            for(int k = 0;k < 4;k++){
                int x = a + dx[k];
                int y = b + dy[k];
                if(x >= 0 && x < n && y >= 0 && y < m && dest[x][y] == -1){
                    if(mat[x][y] == 1){
                        //5.在原来的基础上直接 + 1
                        dest[x][y] = dest[a][b] + 1;
                    }
                    q.add(new int[]{x,y});
                }
            }
        }
        return dest;
    }
}

2.2 例题2:飞地的数量

• 题目链接:飞地的数量

• 问题描述:

给你一个大小为 m x n 的二进制矩阵 grid ,其中 0 表示一个海洋单元格、1 表示一个陆地单元格。

一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过 grid 的边界。

返回网格中 无法 在任意次数的移动中离开网格边界的陆地单元格的数量。

• 解题思路:

正难则反: 从边上的 1 开始搜索,把与边上 1 相连的联通区域全部标记⼀下,然后再遍历⼀遍矩阵,看看哪些位置的 1 没有被标记即可,标记的时候,可以用多源 bfs 解决(这题单源或者 DFS都行)。

• 代码编写:

class Solution {
    int n,m;
    int[] dx = {0,0,1,-1};
    int[] dy = {1,-1,0,0};
    public int numEnclaves(int[][] grid) {
        n = grid.length;
        m = grid[0].length;
        //1.拿所有边界的1进行一次bfs标记
        Queue<int[]> q = new LinkedList<>();
        for(int i = 0;i < n;i++){
            for(int j = 0;j < m;j++){
                if(i == 0 || i == n - 1 || j == 0 || j == m - 1){//这么写比较快且不容易出错
                    if(grid[i][j] == 1){
                        q.add(new int[]{i,j});
                    }
                }
            }
        }
        //直接改为2即可
        while(!q.isEmpty()){
            int size = q.size();
            for(int i = 0;i < size;i++){
                int[] tmp = q.poll();
                int a = tmp[0],b = tmp[1];
                grid[a][b] = 2;
                for(int k = 0;k < 4;k++){
                    int x = a + dx[k];
                    int y = b + dy[k];
                    if(x >= 0 && x < n && y >= 0 && y < m && grid[x][y] == 1){
                        q.add(new int[]{x,y});
                        grid[x][y] = 2;
                    }
                }

            }
        }
        //2.最后遍历如果为 1 且未被标记的话ret++
        int ret = 0;//最后的结果
        for(int i = 0;i < n;i++){
            for(int j = 0;j < m;j++){
                if(grid[i][j] == 1){
                    ret++;
                }
            }
        }
        //3.返回 ret
        return ret;

    }
}

2.3 例题3:地图中的最高点 

• 题目链接:地图中的最高点

• 问题描述:

给你一个大小为 m x n 的整数矩阵 isWater ,它代表了一个由 陆地 和 水域 单元格组成的地图。

  • 如果 isWater[i][j] == 0 ,格子 (i, j) 是一个 陆地 格子。
  • 如果 isWater[i][j] == 1 ,格子 (i, j) 是一个 水域 格子。

你需要按照如下规则给每个单元格安排高度:

  • 每个格子的高度都必须是非负的。
  • 如果一个格子是 水域 ,那么它的高度必须为 0 。
  • 任意相邻的格子高度差 至多 为 1 。当两个格子在正东、南、西、北方向上相互紧挨着,就称它们为相邻的格子。(也就是说它们有一条公共边)

找到一种安排高度的方案,使得矩阵中的最高高度值 最大 。

请你返回一个大小为 m x n 的整数矩阵 height ,其中 height[i][j] 是格子 (i, j) 的高度。如果有多种解法,请返回 任意一个 。

• 解题思路:

01矩阵的变型题,直接用多源 bfs 解决即可。任意相邻的格子高度差 至多 为 1,这个条件非常重要,因为有这个条件我们才能确定这题其实是用最短路的思路来解决。具体就是把所有水域的格子添加到队列中,一层一层遍历即可。

• 代码编写:

class Solution {
    int[] dx = {0,0,1,-1};
    int[] dy = {1,-1,0,0};
    public int[][] highestPeak(int[][] isWater) {
        //多源 bfs
        int n = isWater.length;
        int m = isWater[0].length;
        int[][] ans = new int[n][m];
        //3.把返回数组初始化为 -1
        for(int i = 0;i < n;i++){
            Arrays.fill(ans[i],-1);
        }
        //1.找到所有的值为 1 的加入 bfs
        Queue<int[]> q = new LinkedList<>();
        for(int i = 0;i < n;i++){
            for(int j = 0;j < m;j++){
                if(isWater[i][j] == 1){
                    ans[i][j] = 0;
                    q.add(new int[]{i,j});
                }
            }
        }
        while(!q.isEmpty()){
            int size = q.size();
            for(int i = 0;i < size;i++){
                int[] tmp = q.poll();
                int a = tmp[0],b = tmp[1];//这个地方不用再填值了
                for(int k = 0;k < 4;k++){
                    int x = a + dx[k];
                    int y = b + dy[k];
                    if(x >= 0 && x < n && y >= 0 && y < m && ans[x][y] == -1 && 
                    isWater[x][y] == 0){
                        ans[x][y] = ans[a][b] + 1;//直接在原来的数值上 + 1 即可
                        //不用再来一个time
                        q.add(new int[]{x,y});
                    }
                }
            }
        }
        //4.返回答案
        return ans;
    }
}

2.4 例题4:地图分析

• 题目链接:地图分析

• 问题描述:

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

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

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

• 解题思路:

正难则反,把所有 1 的节点都进队,这样一次就可以把所有的 0 都填好(路径数)。和上面几题都差不多,多源 BFS 代码编写倒是不难,主要是要先转化一下,怎么把那么多节点变成 “超级源点”。

• 代码编写:

class Solution {
    int[] dx = {0,0,1,-1};
    int[] dy = {1,-1,0,0};
    public int maxDistance(int[][] grid) {
        //多源 bfs
        int n = grid.length;
        int m = grid[0].length;
        boolean[][] vis = new boolean[n][m];
        //1.把 1 进 bfs
        Queue<int[]> q = new LinkedList<>();
        for(int i = 0;i < n;i++){
            for(int j = 0;j < m;j++){
                if(grid[i][j] == 1){
                    q.add(new int[]{i,j});
                }
            }
        }
        if(q.size() == 0 || q.size() == (n * m)){
            return -1;
        }
        int time = 0;
        //2.time计数
        while(!q.isEmpty()){
            int size = q.size();
            time++;
            for(int i = 0;i < size;i++){
                int[] tmp = q.poll();
                int a = tmp[0],b = tmp[1];
                vis[a][b] = true;
                for(int k = 0;k < 4;k++){
                    int x = a + dx[k],y = b + dy[k];
                    if(x >= 0 && x < n && y >= 0 && y < m && !vis[x][y] && 
                    grid[x][y] == 0){
                        q.add(new int[]{x,y});
                        //3.vis 去重
                        vis[x][y] = true;
                    }
                }
            }
        }
        return time - 1;
    }
}

• 小结:和单源 BFS 找最短路的模板基本一样,只要多加练习,代码编写是很简单的,重要的是思路,哪些题目可以使用多源 BFS 来解决,这个就需要多做题了👍👍👍。

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/671196.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Qt | Qt 资源简介(rcc、qmake)

1、资源系统是一种独立于平台的机制,用于在应用程序的可执行文件中存储二进制文件(前面所讨论的数据都存储在外部设备中)。若应用程序始终需要一组特定的文件(比如图标),则非常有用。 2、资源系统基于 qmake,rcc(Qt 的资源编译器,用于把资源转换为 C++代码)和 QFile …

Java扩展机制:SPI与Spring.factories详解

一、SPI SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。 整体机制图如下: Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。 系统设计的各个抽象,往往有很多不…

【车载开发系列】汽车开发常用工具说明

【车载开发系列】汽车开发常用工具说明 【车载开发系列】汽车开发常用工具说明 【车载开发系列】汽车开发常用工具说明一. CANbedded二. Davinci Configurator Pro三. Davinci Developer-AUTOSAR软件组件设计工具四. MICROSAR五. MICROSAR OS六. CANdelaStudio七. Volcano VSB八…

css动态导航栏鼠标悬停特效

charset "utf-8"; /*科e互联特效基本框架CSS*/ body, ul, dl, dd, dt, ol, li, p, h1, h2, h3, h4, h5, h6, textarea, form, select, fieldset, table, td, div, input {margin:0;padding:0;-webkit-text-size-adjust: none} h1, h2, h3, h4, h5, h6{font-size:12px…

Nodejs-- 网络编程

网络编程 构建tcp服务 TCP tcp全名为传输控制协议。再osi模型中属于传输层协议。 tcp是面向连接的协议&#xff0c;在传输之前需要形成三次握手形成会话 只有会话形成了&#xff0c;服务端和客户端才能想发送数据&#xff0c;在创建会话的过程中&#xff0c;服务端和客户…

【计算机毕业设计】353微信小程序零食批发交易管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

如何进行时间管理

1.一项调查显示&#xff0c;每个人在睡觉上花费的平均时间大约为25年&#xff0c;这无疑是非常重要的。但排名第二的项目却让人大跌眼镜——看电视是8.3年。接下来分别是工作7.5年、吃饭6年、等待和家务各5年、身体护理4.1年、做白日梦4年&#xff0c;等等。 远远落后的是读书时…

ChatTTS+Python编程搞定语音报时小程序

文字转语音神器Python编程搞定语音报时小程序 今天一个好哥们发了一个文字转语音的AI神器的短视频。这个神器的网站是[ChatTTS - Text-to-Speech for Conversational Scenarios][https://chattts.com/]&#xff0c;如下图所示&#xff1a; 这个开源项目可以从github.com上下载…

[数据集][目标检测]轮胎检测数据集VOC+YOLO格式439张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;439 标注数量(xml文件个数)&#xff1a;439 标注数量(txt文件个数)&#xff1a;439 标注类别…

智慧商砼搅拌车安监运营管理的创新实践

随着城市化进程的加速&#xff0c;商砼搅拌车作为城市建设的重要设备&#xff0c;其安全管理与运营效率直接关系到工程质量和施工进度。近年来&#xff0c;通过引入先进的4G无线视频智能车载终端套件&#xff0c;我们实现了对商砼搅拌车的高精度定位、实时音视频调度、实时油量…

队列——一种操作受限的线性表

队列 队列&#xff08;Queue&#xff09;简称队&#xff0c;也是一种操作受限的线性表&#xff0c;只允许在表的一端进行插入&#xff0c;而在表的另一端进行删除。向队列中插入元素称为入队或进队&#xff0c;删除元素称为出队或离队。队列中的元素是先进先出&#xff08;Fir…

C++设计模式-桥接模式

运行在VS2022&#xff0c;x86&#xff0c;Debug下。 29. 桥接模式 桥接模式将抽象与实现分离&#xff0c;使二者可以独立地变化。 应用&#xff1a;如在游戏开发中&#xff0c;多个角色和多个武器交叉组合时。可以使用桥接模式&#xff0c;定义角色抽象类&#xff0c;武器抽象…

注册北京个体工商户条件和办理时间

在北京这座充满活力的城市中&#xff0c;每天都有无数的创业者怀揣着梦想&#xff0c;踏上创业之路。然而&#xff0c;对于许多初次接触企业注册的人来说&#xff0c;往往对注册流程和时间感到困惑。特别是选择代理服务时&#xff0c;更希望了解一个大概的时间范围。那么&#…

Flutter开发效率提升1000%,Flutter Quick教程之对Widget进行删除,剪切,粘贴

一&#xff0c;删除操作 1&#xff0c;首先我们选中要删除的Widget。 2&#xff0c;在左边的侧边栏&#xff0c;点击删除按钮&#xff0c;即可完成对组件的删除操作。 二&#xff0c;剪切。剪切是相同的道理&#xff0c;都是先选中&#xff0c;再点击对应的按钮。 1&#xff…

拿捏AVL(C++)

文章目录 前言AVL树介绍模拟实现框架查找插入验证删除完整代码 性能分析总结 前言 在本篇文章中我&#xff0c;我们将会介绍一下有关AVL树的相关内容&#xff0c;并且对一些接口进行模拟实现。 AVL树介绍 为什么会有AVL树呢&#xff1f;&#xff1f; 我们在之前学习二叉树时…

UI的学习(一)

UI的学习(一) 文章目录 UI的学习(一)UIlabelUIButtonUIButton的两种形式UIButton的事件触发 UIView多个视图之间的关系 UIWindowUIViewController一个视图推出另一个视图 定时器和视图移动UISwitchUISlider和UIProgressSlid步进器与分栏控制器UITextFieldUIScrollView有关实现它…

【Python打包成exe】

Python打包成exe 前言一、理论知识打底二、实操开始----pyinstaller【Base环境下】【这是一个失败案例】规规矩矩 总结 前言 先放点参考 这个字多&#xff0c;写得很详细⇨用 Pyinstaller 模块将 Python 程序打包成 exe 文件&#xff08;全网最全面最详细&#xff0c;万字详述…

C语言王国——内存函数

目录 1 memcpy函数 1.1 函数表达式 1.2 函数模拟 2 memmove函数 2.1 函数的表达式 2.2 函数模拟 3 memset函数 3.1 函数的表达式 3.2 函数的运用 4 memcmp函数 4.1函数的表达式&#xff1a; 4.2 函数的运用 5 结论 接上回我们讲了C语言的字符和字符串函数&#…

UI案例——登陆系统

UI的登陆界面实例 在学习了UILabel&#xff0c;UIButton&#xff0c;UIView&#xff0c;UITextField的内容之后&#xff0c;我们就可以写一个简单的登陆界面 我们可以通过UILabel来编写我们显示在登陆界面上的文字比方说下面这两行字就是通过UILabel去实现的。 下面给出一下实现…

6.2 休息日 背包问题总结

就目前所遇到的01背包与完全背包作总结。 01背包 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 二维dp数组01背包 动规五部曲 1.确定…