算法:BFS 解决多源最短路问题

news2024/9/27 5:41:55

目录

多源最短路

题目一:矩阵

题目二:飞地的数量

题目三:地图中的最高点

题目四:地图分析


多源最短路

首先想要知道多源最短路,就先要明白单源最短路,bfs解决单源最短路问题前面学习过,单源最短路就是只有一个起点,到终点的最短路径

多源最短路就是有若干个起点,到终点的最短路径

多源bfs就是使用bfs解决多源最短路问题

这里再强调一下,能够使用bfs,前提必须是边权为1的问题

多源最短路问题的解决方法:

解法一:暴力枚举

将每一个起点都进行bfs,每个起点都会获得一个结果,得到的这些结果中最小的那一个就是题目所要求的

这种解答大概率是会超时的,所以学习下面的解法二

解法二:把所有的源点当成一个源点

此时问题就变为了单一的单源最短路问题了

在单源最短路问题中,我们是把一个起点加入队列,再一层一层往外扩展

而多源最短路问题中,我们是把所有起点加入队列,再一层一层往外扩展

其实代码都是差不多的,区别就是刚开始的队列加入的起点数目不同


题目一:01矩阵

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

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

示例 1:

输入:mat = [[0,0,0],[0,1,0],[0,0,0]]
输出:[[0,0,0],[0,1,0],[0,0,0]]

示例 2:

输入:mat = [[0,0,0],[0,1,0],[1,1,1]]
输出:[[0,0,0],[0,1,0],[1,2,1]]

这道题的题意就是求矩阵中每一个位置的元素的值,每个位置的值是距离最近的0的距离,如果本身就是0,那么距离就是0

解法一:每个位置挨个求

在这种解法中,二维数组的每一个位置依次求离最近的0的距离,很显然是会超时的,所以这种解法就排除了

解法二:多源BFS + 正难则反

因为采用的多源bfs的策略,最初的想法就是将所有的1当做一个起点,去找0,得到最小的距离,但是当找到0后,我们无法得知当前的找到的最短距离是哪个位置的1,所以这种策略是不对的

那么正难则反,我们可以将所有的0当做一个起点,去找1,这样找到的结果和上面也是一样的

那么此时当找到1后,就可以将当前的最短步数直接更新到1的位置上即可,

所以分为下面两步:

1、把所有的0加入到队列中

遍历过程中,所有0位置的数据可以直接更新为0,因为自己本身就是0,距离也就是0

2、一层一层向外扩展

扩展过程中需要注意,已经赋过值的位置就不需要重复赋值了,因为第一次赋值的一定是最短的距离

细节问题:

在前面的单源最短路问题中,我们使用了三个元素来解决,分别是:
记录是否重复出现的bool数组vis
每一次出队列的个数sz
记录扩展了step层,这里的step就是最短路径了

而多源最短路问题,不需要上面的三个元素,只需要一个dist数组即可

因为初始化时,将dist中的值全部初始化为-1,然后将原始值是0的继续改为0,此时再bfs过程中,只要是-1的才改变它的值,这里就不需要使用vis来标记了

并且我们变化是从(a,b)变为(x,y),当走到(x,y)后,(x,y)的值就是(a,b)的值+1,所以这里也就不需要之前的sz和step了,直接一个dist数组就能解决,dist数组为-1就表示还没有被搜索过,dist不为-1,就表示当前位置的最短距离

当然了此题也是可以使用这三个全局变量,这里只是说明一下,可以更简便的解题

代码如下:

class Solution 
{
public:
    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>> dist(m, vector<int>(n, -1));
        queue<pair<int, int>> q;
        // 将mat中的0的位置dist该位置也改为0,并将坐标都入队列
        for(int i = 0; i < m; i++)
        {
            for(int j = 0; j < n; j++)
            {
                if(mat[i][j] == 0)
                {
                    dist[i][j] = 0;
                    q.push({i, j});
                }
            }
        }
        // 一层一层往外扩
        while(!q.empty())
        {
            // 不需要计算sz,因为dist的值就可以表示走了几步
            auto [a, b] = q.front();
            q.pop();
            int tmp = dist[a][b];
            for(int k = 0; k < 4; k++)
            {
                int x = a + dx[k];
                int y = b + dy[k];
                if(x >= 0 && x < m && y >= 0 && y < n && dist[x][y] == -1)
                {
                    dist[x][y] = tmp + 1;
                    q.push({x, y});
                }
            }
        }
        return dist; 
    }
};

题目二:飞地的数量

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

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

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

示例 1:

输入:grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]]
输出:3
解释:有三个 1 被 0 包围。一个 1 没有被包围,因为它在边界上。

示例 2:

输入:grid = [[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]]
输出:0
解释:所有 1 都在边界上或可以到达边界。

解法一:暴力解法

在遍历的过程中,找到每一个1,每一个1都去判断,是否能够到达边界

这种解法一定会超时,但是也可以做优化

即找到某一个1时,如果发生这个1能够到达边界,那么此时再遍历一遍,将这个遍历过程中的所有1都做一下标记, 下次就不需要遍历这些做过标记的1了,这种优化可以做到不超时,但是还是有些麻烦的,因为需要bfs两次,下面看解法二

解法二:正难则反

从里面找1到边界需要考虑的事情非常多,但是我可以从边界的所有1开始向里面搜索,此时搜索到的1就一定是可以到达边界的

这种解法使用bfs时,就可以采用多源bfs的方法,将边界所有的1全部添加进队列中,就可以将所有的满足条件的1全部遍历到,最后再找不满足的条件的1的个数即可

代码如下:

class Solution 
{
public:
    typedef pair<int, int> PII; 
    int dx[4] = {0,0,-1,1};
    int dy[4] = {-1,1,0,0};
    bool vis[501][501];

    int numEnclaves(vector<vector<int>>& g) 
    {
        int m = g.size(), n = g[0].size();
        queue<PII> q;
        // 将边界的1加入队列中
        for(int i = 0; i < m; i++)
            for(int j = 0; j < n; j++)
                if(g[i][j] == 1 && (i == 0 || i == m - 1 || j == 0 || j == n - 1))
                {
                    q.push({i, j});
                    vis[i][j] = true;
                }
        // 多源 bfs
        while(!q.empty())
        {
            auto [a, b] = q.front();
            q.pop();
            for(int k = 0; k < 4; k++)
            {
                int x = a + dx[k];
                int y = b + dy[k];
                if(x >= 0 && x < m && y >= 0 && y < n && g[x][y] == 1 && vis[x][y] == false)
                {
                    q.push({x, y});
                    vis[x][y] = true;
                }
            }
        }
        // 统计结果
        int ret = 0;
        for(int i = 0; i < m; i++)
            for(int j = 0; j < n; j++)
                if(g[i][j] == 1 && vis[i][j] == false)
                    ret++;
        return ret;
    }
};

题目三:地图中的最高点

给你一个大小为 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) 的高度。如果有多种解法,请返回 任意一个 。

示例 1:

输入:isWater = [[0,1],[0,0]]
输出:[[1,0],[2,1]]
解释:上图展示了给各个格子安排的高度。
蓝色格子是水域格,绿色格子是陆地格。

示例 2:

输入:isWater = [[0,0,1],[1,0,0],[0,0,0]]
输出:[[1,1,0],[0,1,1],[1,2,2]]
解释:所有安排方案中,最高可行高度为 2 。
任意安排方案中,只要最高高度为 2 且符合上述规则的,都为可行方案。

这道题的题目描述比较长,其实理解一下就是:

原始矩阵的0表示陆地,1表示水域,需要我们返回一个新矩阵,新矩阵的要求如下:

①原始为水域的位置新矩阵要为0
②其余的位置,每次扩展都可以高度加1
③求整个矩阵的最高高度值

理解完题意,可以看出来,也就是多源bfs,从0位置开始扩展,每次扩展到其他位置时,都+1即可,非常简单

代码如下:

class Solution 
{
public:
    int dx[4] = {0,0,-1,1};
    int dy[4] = {-1,1,0,0};
    typedef pair<int, int> PII;

    vector<vector<int>> highestPeak(vector<vector<int>>& isWater) 
    {
        int m = isWater.size(), n = isWater[0].size();
        vector<vector<int>> height(m, vector<int>(n, -1));
        queue<PII> q;
        // 将所有源点(水域)加入队列
        for(int i = 0; i < m; i++)
            for(int j = 0; j < n; j++)
                if(isWater[i][j] == 1)
                {
                    height[i][j] = 0;
                    q.push({i, j});
                }
        // 多源bfs
        while(!q.empty())
        {
            auto [a, b] = q.front();
            q.pop();
            for(int k = 0; k < 4; k++)
            {
                int x = a + dx[k];
                int y = b + dy[k];
                if(x >= 0 && x < m && y >= 0 && y < n && height[x][y] == -1)
                {
                    // 每次的高度是之前高度+1
                    height[x][y] = height[a][b] + 1;
                    q.push({x, y});
                }
            }
        }
        return height;
    }
};

题目四:地图分析

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

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

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

示例 1:

输入:grid = [[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释: 
海洋单元格 (1, 1) 和所有陆地单元格之间的距离都达到最大,最大距离为 2。

示例 2:

输入:grid = [[1,0,0],[0,0,0],[0,0,0]]
输出:4
解释: 
海洋单元格 (2, 2) 和所有陆地单元格之间的距离都达到最大,最大距离为 4。

同样,此题也非常简单,简单分析一下:

0表示海洋,1表示陆地,请找到海洋距离最远的陆地,如果整个矩阵只有海洋或是陆地,就返回-1

这道题的解法和前面几乎一模一样, 找0到1的最大距离,这个并不好找

我们同样正难则反,找1到0的最远距离,每次扩展一层就+1,最后返回最大的值即可

代码如下:

class Solution 
{
public:
    int dx[4] = {0,0,-1,1};
    int dy[4] = {-1,1,0,0};
    typedef pair<int, int> PII;

    int maxDistance(vector<vector<int>>& grid) 
    {
        int m = grid.size(), n = grid[0].size();
        vector<vector<int>> dist(m, vector<int>(n, -1));
        queue<PII> q;
        for(int i = 0; i < m; i++)
            for(int j = 0; j < n; j++)
                if(grid[i][j] == 1)
                {
                    dist[i][j] = 0;
                    q.push({i, j});
                }
        int ret = -1;
        while(!q.empty())
        {
            auto [a, b] = q.front();
            q.pop();
            for(int k = 0; k < 4; k++)
            {
                int x = a + dx[k];
                int y = b + dy[k];
                if(x >= 0 && x < m && y >= 0 && y < n && dist[x][y] == -1)
                {
                    q.push({x, y});
                    dist[x][y] = dist[a][b] + 1;
                    ret = max(ret, dist[x][y]);
                }
            }
        }
        return ret;
    }
};

BFS 解决多源最短路问题到此结束

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

leetcode-二叉树oj题1(共三道)--c语言

目录 a. 二叉树的概念以及实现参照博客&#xff1a; 一、三道题的oj链接 二、每题讲解 1.单值二叉树 a. 题目&#xff1a; b. 题目所给代码 c. 思路 d. 代码&#xff1a; 2. 相同的树 a. 题目 b. 题目所给代码 c. 思路 d. 代码 3. 二叉树的前序遍历 a. 题目 b.…

J029_UDP通信

一、需求描述 实现UDP的通信 1.1 一发一收 1.1.1 ClientTest1 package com.itheima.udp;import java.net.*;import static java.net.InetAddress.*;//完成udp通信快速入门&#xff0c;实现一收一发 public class ClientTest1 {public static void main(String[] args) thro…

递归 35

方法递归 递归算法 package File;public class digui {public static void main(String[] args) {//猴子吃桃//f(10)1//f(n)-f(n)/2—1f&#xff08;n1&#xff09;//f(n)F(n1)2System.out.println(f(3));}public static int f(int n){if (n10){return 1;}else {return 2*f(n1)…

MEME币热潮结束了?上市成功率仅1.4%!迷因暴富梦醒?洗量超容易,热潮都是假?

近年来&#xff0c;随着加密货币行业的蓬勃发展&#xff0c;各种迷因币(meme coins)也在此浪潮之中纷纷崛起。然而&#xff0c;在专门用于创造迷因币的平台"pump.fun"上&#xff0c;绝大多数迷因币都无法真正成功发行和上市。 最新的数据显示&#xff0c;近日Solana迷…

golang国内proxy设置

go env -w GOPROXYhttps://goproxy.cn,direct经常使用的两个, goproxy.cn 和 goproxy.io 连接分别是 https://goproxy.cn https://goproxy.io 如果遇到某些包下载不下来的情况&#xff0c;可尝试更换数据源 更推荐使用https://goproxy.cn 速度快&#xff0c;缓存的包多 提醒…

树莓派5进行YOLOv8部署的4种不同部署方式检测速度对比:pytorch、onnx、ncnn、tflite

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

关于k8s集群的资源发布方式(灰度/滚动发布)

目录 1.常见的发布方式 2.实现蓝绿发布 3.实现金丝雀发布&#xff08;Canary Release&#xff09; 4.声明式管理方法 1.常见的发布方式 蓝绿发布:两套环境交替升级&#xff0c;旧版本保留一定时间便于回滚优点&#xff1a;用户无感知&#xff0c;部署和回滚速度较快&#…

如何统计visiual studio代码行数

统计Visual Studio中的代码行数&#xff0c;可以通过Visual Studio自带的查找功能结合正则表达式来实现。以下是一个详细的步骤说明&#xff1a; 一、使用Visual Studio的查找功能 打开Visual Studio&#xff1a;首先&#xff0c;确保你已经打开了Visual Studio并加载了你想要…

pyqt中使用opengl绘制图像

首先有在C中使用的opengl基础 在qt designer中&#xff0c;可以直接找到 QOpenGLWidget类 &#xff0c;是一个黑框框&#xff08;图1&#xff09; 也就是说&#xff0c;不需要安装额外的东西&#xff0c;一切从简 然后就是看官方文档学习啦 intializeGL() 在这里面设定好…

JavaScript基础——数据类型转换

显示数据类型转换 String()函数进行显示转换 Number()函数进行显示转换 Boolean()函数进行显示转换 隐式数据类型转换 算术运算隐式转化 比较操作隐式转化 赋值操作 在JavaScript中&#xff0c;数据类型转换是常见的操作&#xff0c;它允许将一种类型的数据转换为另一种…

c++网络编程实战——开发基于协议的文件传输模块(一)如何实现一个简单的tcp长连接

前言 在之前的几篇内容中我们已经介绍过基于ftp协议的文件传输模块&#xff0c;而这个系列我们所想实现的就是如何实现基于tcp进行的文件传输模块,话不多说&#xff0c;开坑开坑! 什么是tcp长连接 我们知道tcp在建立连接的时候会通过三次握手与四次挥手来建立tcp连接&#x…

用uniapp 及socket.io做一个简单聊天app 4

界面如下&#xff1a; <template><view class"container"><input v-model"username" placeholder"用户名" /><input v-model"password" type"password" placeholder"密码" /><butto…

探秘北京崇文门中医医院卫景沛医生:为何深受患者信赖?

卫景沛是北京崇文门中医医院特聘专家&#xff0c;深受患者信赖&#xff01; 北京崇文门中医医院卫景沛主任毕业于兰州大学医学院&#xff0c;拥有医学硕士学位。他的硕士导师是天坛医院的王拥军教授&#xff0c;主要研究方向为脑血管病及脑血管病介入治疗。 为了提升自己在缺血…

对称加密:数据安全的保障

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Qt对象树的介绍

目录 创建项目&#xff08;此处我就不多介绍了&#xff09; 按钮 对象树 创建项目&#xff08;此处我就不多介绍了&#xff09; QMainWidow带菜单栏的 QWidget空白的 QDialog对话框 创建功能时注意&#xff1a; 项目工程名称一般不要有标点&#xff0c;不要带中文 按钮 /…

计算机基础(Windows 10+Office 2016)教程 —— 第8章 多媒体技术及应用

多媒体技术及应用 8.1 多媒体技术的概述8.1.1 多媒体技术的定义和特点8.1.2  多媒体的关键技术8.1.3 多媒体技术的发展趋势8.1.4 多媒体文件格式的转换8.1.5 多媒体技术的应用 8.2 多媒体计算机系统的构成8.2.1 多媒体计算机系统的硬件系统8.2.2 多媒体计算机系统的软件系统…

Python教程(十一):单元测试与异常捕获

目录 专栏列表前言一、Python中的测试1.1 单元测试1.1.1 定义测试类1.2.1 安装 pytest1.2.2 编写测试1.2.3 运行测试 二、Python中的异常捕获2.1 常规代码2.2 异常基础 三、抛出异常&#xff08;异常传播&#xff09;四、 自定义异常 专栏列表 Python教程&#xff08;一&#…

赛蓝企业管理系统 AuthToken/Index 身份认证绕过漏洞复现

0x01 产品简介 赛蓝企业管理系统是一款为企业提供全面管理解决方案的软件系统&#xff0c;它能够帮助企业实现精细化管理&#xff0c;提高效率&#xff0c;降低成本。系统集成了多种管理功能&#xff0c;包括但不限于项目管理、财务管理、采购管理、销售管理以及报表分析等&am…

【WPF开发】如何将工程打包成单独的EXE安装包

一、安装NSIS与HM NIS Edit 1、下载和安装NSIS NSIS官网 2、下载和安装HM NIS Edit HM NIS Edit官网 点击下载后等待几秒&#xff0c;就会弹出下载提示 双击下载的安装包&#xff0c;点击“OK” 点击“下一步” 点击“我接受” 更改路径后&#xff0c;点击安装即可 二、打包软…

SpringSecurity-1(认证和授权+SpringSecurity入门案例+自定义认证+数据库认证)

SpringSecurity 1 初识权限管理1.1 权限管理的概念1.2 权限管理的三个对象1.3 什么是SpringSecurity 2 SpringSecurity第一个入门程序2.1 SpringSecurity需要的依赖2.2 创建web工程2.2.1 使用maven构建web项目2.2.2 配置web.xml2.2.3 创建springSecurity.xml2.2.4 加载springSe…