BFS 解决最短路问题

news2024/11/18 10:21:40

目录

一、前言

1.1 如何使用 BFS 找到最短路:

1.2 为什么不用 dfs :

二、模板套路

三、例题练习

3.1 例题1:迷宫中离入口最近的出口

3.2 例题2:最小基因变化

3.3 例题3:单词接龙

3.4 例题4:为高尔夫比赛砍树


一、前言

最短路问题一般我们都是在图论中会遇到的问题,对应的算法有 Dijkstra 算法,Bellman-Ford 算法,Floyd-Warshall 算法。上面的三个算法后续在图论的文章里面会单独再介绍(内容比较多),今天主要介绍使用 BFS 来解决边权为 1 的最短路问题,边权为 1 的这个条件可以衍生为边权全部相同。为了方便叙述下面所有的最短路问题都是边权全部相同的情况。

1.1 如何使用 BFS 找到最短路:

解法:从起点开始,来一次 BFS 即可。扩展的层数就是最短路的长度,一旦遍历到终点立即返回对应的层数,就是最短路的长度。对应过程如下图 BFS 就是对应每条路径(不同颜色)同时在周围扩散一步。

1.2 为什么不用 dfs :

使用 dfs 大概率会超时,因为:bfs 不用遍历所有节点,找到直接返回就是最小值。而 dfs 必须要把全部路径都找一遍才能找到最小值,时间复杂度是比较高的,所以这类问题我们一般使用 bfs 来解决。

二、模板套路

• 参数解释:

map:对应查找数组。

sr | sc:起点坐标。

er | ec:终点坐标。

path:记录路径长度。

vis:去重。

public int bfs(char[][] map,int sr,int sc,int er,int ec){
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{sr,sc});//先放入起点
        int path = 0;//记录路径长度
        while(!queue.isEmpty()){
            int size = queue.size();
            path++;//向外扩展一层
            for(int i = 0;i < size;i++){
                int[] tmp = queue.poll();
                int a = tmp[0],b = tmp[1];
                vis[a][b] = true;
                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 && 题目对应条件 &&
                            !vis[x][y]){
                        if(达到出口条件){
                            return path;//返回
                        }
                        queue.offer(new int[]{x,y});
                        vis[x][y] = true;
                    }
                }
            }

        }
        return -1;//没找到的情况,具体返回什么看题目
    }

上面就是大体的框架,默认起点不会是终点(题目要求可以的话,来个特判即可),如果对 BFS 不是很熟悉的话可以结合 BFS解决FloodFIll算法 来学习。

三、例题练习

3.1 例题1:迷宫中离入口最近的出口

• 题目链接:迷宫中离入口最近的出口

• 问题描述:

给你一个 m x n 的迷宫矩阵 maze (下标从 0 开始),矩阵中有空格子(用 '.' 表示)和墙(用 '+' 表示)。同时给你迷宫的入口 entrance ,用 entrance = [entrancerow, entrancecol] 表示你一开始所在格子的行和列。

每一步操作,你可以往  或者  移动一个格子。你不能进入墙所在的格子,你也不能离开迷宫。你的目标是找到离 entrance 最近 的出口。出口 的含义是 maze 边界 上的 空格子entrance 格子 不算 出口。

请你返回从 entrance 到最近出口的最短路径的 步数 ,如果不存在这样的路径,请你返回 -1 。

• 解题思路:

利用 BFS 来解决是这类题目最经典(边权为 1 的最短路问题)的做法。从起点开始 BFS ,用 path 来记录当前遍历的层数,这样就能在找到出口的时候,返回起点到出口的最短长度。基本就是套模板即可,不同的是终点是在边界地方而不是作为 bfs 参数。

• 代码编写:

class Solution {
    int n,m;
    boolean[][] vis;
    int[] dx = {0,0,1,-1};
    int[] dy = {1,-1,0,0};
    public int nearestExit(char[][] maze, int[] entrance) {
        n = maze.length;
        m = maze[0].length;
        vis = new boolean[n][m];
        int ans = bfs(maze,entrance[0],entrance[1]);
        return ans;
    }
    public int bfs(char[][] map,int sr,int sc){
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{sr,sc});
        int path = 0;
        while(!queue.isEmpty()){
            int size = queue.size();
            path++;//向外扩展一层
            for(int t = 0;t < size;t++){
                 int[] tmp = queue.poll();
                 int a = tmp[0],b = tmp[1];
                 vis[a][b] = true;
                 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 && map[x][y] == '.' && 
                    !vis[x][y]){
                        if(x == 0 || x == n - 1 || y == 0 || y == m - 1){
                            return path;
                        }
                        queue.offer(new int[]{x,y});
                        vis[x][y] = true;
                    }
                 }
            }
           
        }
        return -1;
    }
}

3.2 例题2:最小基因变化

• 题目链接:最小基因变化

• 问题描述:

基因序列可以表示为一条由 8 个字符组成的字符串,其中每个字符都是 'A''C''G' 和 'T' 之一。

假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。

  • 例如,"AACCGGTT" --> "AACCGGTA" 就是一次基因变化。

另有一个基因库 bank 记录了所有有效的基因变化,只有基因库中的基因才是有效的基因序列。(变化后的基因必须位于基因库 bank 中)

给你两个基因序列 start 和 end ,以及一个基因库 bank ,请你找出并返回能够使 start 变化为 end 所需的最少变化次数。如果无法完成此基因变化,返回 -1 。

注意:起始基因序列 start 默认是有效的,但是它并不一定会出现在基因库中。

• 解题思路:

首先因为字符变化并没有权重,所以这是边权为 1 的最短路问题。那么如何枚举出所有的变化情况呢?答:暴力,因为题目的数据都很小,我们可以把字符串每个位置的字符都用 'A','C','G','T'来替换看看在不在基因库中存在,如果存在且没有被找过,存入到队列中。我们可以使用语言带的哈希表来标记搜索过的地方。

优化:我们预先处理基因库,把基因库里面的数据存入到哈希表中,这样就可以用O(1)的时间复杂度来快速找到。

• 代码编写:

class Solution {
    public int minMutation(String startGene, String endGene, String[] bank) {
        // BFS
        // 1.创建 哈希表 来快速判断
        Set<String> hash = new HashSet<>();// 用来快速判断一个字符串是否再bank里面出现
        Set<String> vis = new HashSet<>();// 用来标记已经变化过的字符串
        char[] change = { 'A', 'C', 'G', 'T' };
        for (String tmp : bank) {
            hash.add(tmp);
        }
        if (startGene.equals(endGene)) {// 处理边界情况
            return 0;
        }
        if (!hash.contains(endGene)) {
            return -1;
        }
        Queue<String> queue = new LinkedList<>();
        queue.offer(startGene);//存入开始位置
        int path = 0;
        while (!queue.isEmpty()) {
            path++;// 代表剥离一层
            int size = queue.size();
            for (int i = 0; i < size; i++) {// 找出全部变化
                String tmp = queue.poll();
                vis.add(tmp);
                for (int j = 0; j < 8; j++) {// 把8个位置上的元素全部修改
                    char[] s = tmp.toCharArray();//细节问题,不能放在外面,因为放在
                    //外面那么就不能保证只修改一个元素了
                    for (int k = 0; k < 4; k++) {
                        s[j] = change[k];
                        String next = new String(s);//char[]是不能toString的
                        if (next.equals(endGene)) {//找到出口
                            return path;
                        }
                        if (hash.contains(next) && !vis.contains(next)) {
                            queue.offer(next);
                            vis.add(next);
                        }
                    }
                }
            }
        }
        return -1;
    }
}

3.3 例题3:单词接龙

• 题目链接:单词接龙

• 问题描述:

字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk

  • 每一对相邻的单词只差一个字母。
  •  对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
  • sk == endWord

给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0 。

• 解题思路:

这题可以说是例题2的升级版,因此基本解法是一样的,区别就是例题2是 4 个字符替换,本题是26个字符替换。注意这题找不到是返回 0。

优化:如果 endWord 不在 wordList 中直接返回 0 即可。

• 代码编写:

class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        //最短路径问题,权值是相同的
        //采用 BFS 来解决
        char[] change = new char[26];
        for(int i = 0;i < 26;i++){
            change[i] = (char)(i + 'a');
        }
        Set<String> hash = new HashSet<>();//用来记录字典
        Set<String> set = new HashSet<>();//用来去重
        //处理一些边界情况
        //题目说明了不会出现这种情况
        for(String s:wordList){
            hash.add(s);
        }
        if(!hash.contains(endWord)){//不存在的情况
            return 0;
        }
        Queue<String> queue = new LinkedList<>();
        queue.offer(beginWord);
        int n = beginWord.length();//每个单词有多长
        //进行 BFS 查找
        int path = 1;//用来记录层数
        while(!queue.isEmpty()){
            int size = queue.size();
            path++;
            for(int i = 0;i < size;i++){
                String next = queue.poll();
                set.add(next);
                for(int j = 0;j < n;j++){
                    char[] s = next.toCharArray();//方便替换
                    for(int k = 0;k < 26;k++){
                        s[j] = change[k];
                        String tmp = new String(s);
                        if(tmp.equals(endWord)){//找到答案
                            return path;
                        }
                        if(hash.contains(tmp) && !set.contains(tmp)){
                            queue.offer(tmp);
                            set.add(tmp);//标记为找到了
                        }
                    }
                }
            }
        } 
        return 0;//注意这题找不到是返回0
    }
}

3.4 例题4:为高尔夫比赛砍树

• 题目链接:为高尔夫比赛砍树

• 问题描述:

你被请来给一个要举办高尔夫比赛的树林砍树。树林由一个 m x n 的矩阵表示, 在这个矩阵中:

  • 0 表示障碍,无法触碰
  • 1 表示地面,可以行走
  • 比 1 大的数 表示有树的单元格,可以行走,数值表示树的高度

每一步,你都可以向上、下、左、右四个方向之一移动一个单位,如果你站的地方有一棵树,那么你可以决定是否要砍倒它。

你需要按照树的高度从低向高砍掉所有的树,每砍过一颗树,该单元格的值变为 1(即变为地面)。

你将从 (0, 0) 点开始工作,返回你砍完所有树需要走的最小步数。 如果你无法砍完所有的树,返回 -1 。

可以保证的是,没有两棵树的高度是相同的,并且你至少需要砍倒一棵树。

• 解题思路:

1. 找出砍树的顺序。

2. 按照砍树的顺序,一个一个的用 bfs 求出最短路即可(封装成一个函数)。

• 代码编写:

直接利用语言自带 sort 排序,注意每次传入 bfs 求最短路的起点和终点每次都不一样,要一直更新。

class Solution {
    int n,m;
    public int cutOffTree(List<List<Integer>> forest) {
        n = forest.size();
        m = forest.get(0).size();
        //1.把不为0的数的下标存入list
        List<int[]> ret = new ArrayList<>();
        for(int i = 0;i < n;i++){
            for(int j = 0;j < m;j++){
                if(forest.get(i).get(j) > 1){
                    ret.add(new int[]{i,j});
                }
            }
        }
        //2.排序
        Collections.sort(ret,(o1,o2) -> {
            return forest.get(o1[0]).get(o1[1]) > forest.get(o2[0]).get(o2[1]) ? 1 : -1;
        });//从小到大排序
        //3.从小到大用dfs找,找不到返回-1
        int path = 0;
        int sum = 0;//最后全部的和
        int x = 0,y = 0;//起点
        for(int[] tmp:ret){
            path = bfs(forest,x,y,tmp[0],tmp[1]);
            if(path == -1){
                return -1;//找不到立即返回 -1
            }
            x = tmp[0];y = tmp[1];//每个起点是不一样的,要一直更新
            sum += path;
        }
        return sum;
    }
    int[] dx = {0,0,1,-1};
    int[] dy = {1,-1,0,0};
    public int bfs(List<List<Integer>> f, int bx, int by, int ex, int ey)
    {
        if(bx == ex && by == ey) return 0;//可能起点即终点
        Queue<int[]> q = new LinkedList<>();
        boolean[][] vis = new boolean[n][m];
        q.add(new int[]{bx, by});
        vis[bx][by] = true;
        int step = 0;
        while(!q.isEmpty())
        {
            int sz = q.size();
            step++;
            while(sz-- != 0)
            {
                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 && x < n && y >= 0 && y < m && f.get(x).get(y)
                            != 0 && !vis[x][y])
                    {
                        if(x == ex && y == ey) return step;
                        q.add(new int[]{x, y});
                        vis[x][y] = true;
                    }
                }
            }

        }
        return -1;
    }
}

结语:

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

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

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

相关文章

Springboot+Vue+ElementUI开发前后端分离的员工管理系统01--系统介绍

项目介绍 springboot_vue_emp是一个基于SpringbootVueElementUI实现的前后端分离的员工管理系统 功能涵盖&#xff1a; 系统管理&#xff1a;用户管理、角色管理、菜单管理、字典管理、部门管理出勤管理&#xff1a;请假管理、考勤统计、工资发放、工资统计、离职申请、个人资…

低频量化周报(指数分位值,指数风险溢价比,配债完整数据集,可转债策略)...

低频量化周报&#xff08;2024-05-25&#xff09; 指数分位值指数风险溢价比小规模配债<5亿配债完整数据 5 批文通过4 发哥通过3 交易所受理2 股东大会通过1 董事会预案可转债策略 双低策略四因子策略网格策略ETF抄底指标<3历史操作记录本周心得最后 指数分位值 指数名称…

秋招突击——算法打卡——5/25、5/26——寻找两个正序数组的中位数

题目描述 自我尝试 首先&#xff0c;就是两个有序的数组进行遍历&#xff0c;遍历到一半即可。然后求出均值&#xff0c;下述是我的代码。但这明显是有问题的&#xff0c;具体错误的代码如下。计算复杂度太高了&#xff0c;O&#xff08;n&#xff09;&#xff0c;所以会超时&…

2024年【高压电工】新版试题及高压电工找解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 高压电工新版试题是安全生产模拟考试一点通生成的&#xff0c;高压电工证模拟考试题库是根据高压电工最新版教材汇编出高压电工仿真模拟考试。2024年【高压电工】新版试题及高压电工找解析 1、【单选题】 110KV及以下…

C++课程设计:学校人员信息管理系统(可视化界面)

目录 学校人员信息管理系统 操作演示 MP4转GIF动图 设计功能要求 评分标准 QT Creator安装和新建项目 QT安装 QT新建项目 管理系统程序设计 mainwindow.h 文件 mainwindow.h 程序释义 mainwindow.cpp 文件 mainwindow.cpp 程序释义 main.h 文件 TXT文件生成 博主…

redis6.2.7 搭建一主多从

1、集群规划 节点端口角色192.168.137.1026379master192.168.137.1026380slave192.168.137.1036381slave 2、伪集群搭建 2.1 创建fake_cluster 目录存放 公共配置文件 # 进入redis目录 cd /app/apps/redis-6.2.7# 创建存放伪集群的目录 mkdir fake_cluster#复制redis.conf到…

东方通TongWeb结合Spring-Boot使用

一、概述 信创需要; 原状:原来的服务使用springboot框架,自带的web容器是tomcat,打成jar包启动; 需求:使用东方通tongweb来替换tomcat容器; 二、替换步骤 2.1 准备 获取到TongWeb7.0.E.6_P7嵌入版 这个文件,文件内容有相关对应的依赖包,可以根据需要来安装到本地…

【Qt 学习笔记】Qt窗口 | 菜单栏 | QMenuBar的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt窗口 | 菜单栏 | QMenuBar的使用及说明 文章编号&#xff1a;Qt 学习…

顺序栈的实现

顺序栈是用数组实现的&#xff1a;&#xff08;假设我们有8个位置&#xff08;下标0-7&#xff09;&#xff09; #include<stdio.h> #include<malloc.h> #include<assert.h> #include <iostream> #define MAX_SIZE 8#define Elemtype int typedef str…

Android 配置 Kapt 编译器插件

以 Android Studio 2023.3.1 最新版本为准。 步骤1:打开版本信息配置文件 找到libs.versions.toml文件。 这是打开后的样子&#xff1a; 步骤2&#xff1a;配置版本信息 你需要在[plugins]下面添加一条kapt的配置信息&#xff1a; 要添加的配置信息如下&#xff1a; jetbr…

YOLOv8_pose的训练、验证、预测及导出[关键点检测实践篇]

1.关键点数据集划分和配置 从上面得到的数据还不能够直接训练,需要按照一定的比例划分训练集和验证集,并按照下面的结构来存放数据,划分代码如下所示,该部分内容和YOLOv8的训练、验证、预测及导出[目标检测实践篇]_yolov8训练测试验证-CSDN博客是重复的,代码如下: …

粒子辐照环境中相机镜头防护及LabVIEW图像处理注意事项

在粒子辐照环境测试电路板性能的实验中&#xff0c;需要对相机镜头进行有效防护&#xff0c;同时利用LabVIEW进行图像识别和处理。本文将讨论相机镜头防护的关键因素和LabVIEW处理过程中的注意事项&#xff0c;包括防辐射材料选择、辐射屏蔽措施、散热管理、空间布局及LabVIEW软…

CorelCAD v2022.5 解锁版 安装教程(2D制图 3D设计和打印的简化软件)

前言 CorelCAD&#xff0c;加拿大Corel公司开发的一款适用于2D制图、3D设计和打印的简化版CAD软件。它是款专业的2D制图和3D设计软件&#xff0c;拥有行业标准文件兼容性&#xff0c;支持 .DWG、.STL、.PDF、 .CDR*等文件格式&#xff0c;轻松实现协作和项目共享&#xff0c;利…

6818 android 修改开机 logo, 编译脚本分析

问题&#xff1a; 客户需要去掉 android5.1 的开机logo. 说明&#xff1a; 对于Android5.1 来说&#xff0c;uboot 与kernel 的logo 是一个。 过程&#xff1a; 其实对于开机logo 的修改很简单&#xff0c;直接参考厂家手册就可以了。 这是 android4.4 的开机logo 的修改&…

Qt | QStackedLayout 类(分组布局或栈布局)、QStackedWidget

01、QStackedLayout 类 1、使用 QStackedLayout 可以实现一个多页面切换的界面,多 页面切换就是类似于选项卡(如右图)类型的界面。 2、QStackedLayout 并没有直接实现多页面切换的办面,只是我们可以通过该类实现多页面切 换的功能,因此要使用 QStackedLayout 类实现多面面…

LeetCode 第399场周赛个人题解

100323. 优质数对的总数 I 原题链接 100323. 优质数对的总数 I 思路分析 签到题 AC代码 class Solution:def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:n, m len(nums1), len(nums2)ret 0for i in range(n):for j in range(m):if nu…

GDPU JavaWeb mvc模式

搭建一个mvc框架的小实例。 简易计算器 有一个名为inputNumber.jsp的页面提供一个表单&#xff0c;用户可以通过表单输入两个数和运算符号提交给Servlet控制器&#xff1b;由名为ComputerBean.java生成的JavaBean负责存储运算数、运算符号和运算结果&#xff0c;由名为handleCo…

qt-C++笔记之使用QtConcurrent异步地执行槽函数中的内容,使其不阻塞主界面

qt-C笔记之使用QtConcurrent异步地执行槽函数中的内容&#xff0c;使其不阻塞主界面 code review! 参考博文&#xff1a; qt-C笔记之使用QtConcurrent异步地执行槽函数中的内容&#xff0c;使其不阻塞主界面 qt-C笔记之QThread使用 文章目录 qt-C笔记之使用QtConcurrent异步地…

23种设计模式顺口溜

口诀&#xff1a; 原型 抽风 &#xff0c;单独 建造 工厂 &#xff08;寓意&#xff1a;&#xff08;这里代指本来很简单的东西&#xff0c;却要干工厂这里复杂的业务&#xff09; 抽风&#xff1a;抽象工厂单独&#xff1a;单例桥代理组合享元适配器&#xff0c;&#xff0…

【SpringBoot】SpringBoot中防止接口重复提交(单机环境和分布式环境)

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 &#x1f33c;前言 &#x1f512;单机环境下防止接口重复提交 &#x1f4d5;导入依赖 &#x1f4c2;项目结构 &#x1f680;创建自定义注解 ✈创建AOP切面 &#x1f697;创建Conotroller &#x1f4bb;分布…