算法.图论-bfs及其拓展

news2024/11/28 5:40:40

文章目录

    • 广度优先搜索简介
    • 经典bfs习题
      • 地图分析
      • 贴纸拼词
    • 01bfs解析
      • 基本过程
      • 相关习题

广度优先搜索简介

  1. bfs的特点是逐层扩散, 从源头到目标点扩散了几层, 最短路就是多少

  2. bfs的使用特征是任意两个节点的距离(权值)是相同的(无向图, 矩阵天然满足这一特点)

  3. bfs开始的时候可以是单个源头, 也可以多个源头(单源bfs, 和多源bfs)

  4. bfs进出队列的时候可以是单点弹出, 也可以是整层弹出

    如果是单点弹出的时候, 队列中存放的是当前的节点和距离源点的距离

    整层弹出则不需要, 只需要保留一个level计数就可以知道到源点的距离

  5. bfs进行时通常需要一个visit数组(一般是boolean[][])来标记已经遍历到的位置

  6. bfs的时候一个点向四个方向遍历的时候通常可以用一个move数组搞定(下面是举例)

    //建立一个全局的move数组来进行四个方向的遍历(上, 右, 下, 左)
    private static final int[] move = new int[]{-1, 0, 1, 0, -1};
    //假设下面的函数是用来进行 (i, j) 的遍历的
    private static void traversal(int i, int j, int[][] matrix, boolean[][] visit){
        //不用写四个if, 仅仅需要进行for循环四次就可以了
        int r = matrix.length;
        int c = matrix[0].length;
        for(int k = 0; k < 4; k++){
            int ni = i + move[k];
            int nj = j + move[k + 1];
            if(ni >= 0 && ni < r && nj >= 0 && nj < c && !visit[ni][nj]){
                //下一个位置不越界并且没有访问过
                //.....进行处理逻辑, 并最终把visit数组的这一个位置置为true
                visit[ni][nj] = true;
            }
        }
    }
    
  7. bfs设计的时候有很多的剪枝的操作需要进行一定的摸索

经典bfs习题

地图分析

链接: leetcode1162.地图分析

题目简介:
在这里插入图片描述

解释一下什么是曼哈顿距离, 就是一个点到另外一个点的横坐标的差值和纵坐标的差值之和, 这与我们习惯认为的对角线距离区别开

这种距离的定义通常用于矩形的表格之中(实质上: bfs最广的应用就是矩形格之中, 因为这是一种天然的无向图)

这道题本质上是要找距离陆地最近的海洋的最远的距离, 翻译成人话就是寻找距离陆地最远的海洋, 那我们直接以陆地为源点开始进行bfs即可

我们下面给出来两种实现的方案

第一种是单点弹出的方法

    //创建一个move数组
    private static final int[] move = new int[]{-1, 0, 1, 0, -1};

    //创建一个全局的visit数组
    private static final int MAXM = 101;

    private static final boolean[][] visit = new boolean[MAXM][MAXM];

    //方法一: 单点弹出的方式
    public int maxDistance(int[][] grid) {
        int r = grid.length;
        int c = grid[0].length;
        int seas = 0;
        Queue<int[]> q = new ArrayDeque<>();
        //遍历一下grid数组初始化队列元素同时初始化visit数组
        for(int i = 0; i < r; i++){
            for(int j = 0; j < c; j++){
                if(grid[i][j] == 1){
                    visit[i][j] = true;
                    q.offer(new int[]{i, j, 0});
                }else{
                    visit[i][j] = false;
                    seas++;
                }
            }
        }
        //特殊条件直接返回
        if(seas == r * c || seas == 0){
            return -1;
        }
        //进行bfs的主流程
        int distanse = 1;
        while(!q.isEmpty()){
            int[] cur = q.poll();
            //向四个方向尝试扩展
            for(int k = 0; k < 4; k++){
                int nx = cur[0] + move[k];
                int ny = cur[1] + move[k + 1];
                if(nx >= 0 && nx < r && ny >= 0 && ny < c && !visit[nx][ny]){
                    visit[nx][ny] = true;
                    q.offer(new int[]{nx, ny, cur[2] + 1});
                    distanse = Math.max(distanse, cur[2] + 1);
                }
            }
        }
        return distanse;
    }
}

第二种就是整层弹出的方法

class Solution {
    //创建一个move数组
    private static final int[] move = new int[]{-1, 0, 1, 0, -1};

    //创建一个全局的visit数组
    private static final int MAXM = 101;

    private static final boolean[][] visit = new boolean[MAXM][MAXM];

    //方法二 : 整层弹出的方式
    public int maxDistance(int[][] grid) {
        int r = grid.length;
        int c = grid[0].length;
        int seas = 0;
        Queue<int[]> q = new ArrayDeque<>();
        //遍历一下grid数组初始化队列元素同时初始化visit数组
        for(int i = 0; i < r; i++){
            for(int j = 0; j < c; j++){
                if(grid[i][j] == 1){
                    visit[i][j] = true;
                    q.offer(new int[]{i, j});
                }else{
                    visit[i][j] = false;
                    seas++;
                }
            }
        }
        //特殊条件直接返回
        if(seas == r * c || seas == 0){
            return -1;
        }
        //进行bfs的主流程
        int level = 0;
        while(!q.isEmpty()){
            level++;
            int sz = q.size();
            while(sz-- != 0){
                int[] cur = q.poll();
                //尝试向四个方向扩展
                for(int k = 0; k < 4; k++){
                    int nx = cur[0] + move[k];
                    int ny = cur[1] + move[k + 1];
                    if(nx >= 0 && nx < r && ny >= 0 && ny < c && !visit[nx][ny]){
                        q.offer(new int[]{nx, ny});
                        visit[nx][ny] = true;
                    }
                }
            }
        }
        return level - 1;
    }
}

贴纸拼词

链接: [leetcode691.贴纸拼词](. - 力扣(LeetCode))

题目描述: 在这里插入图片描述

这个题的解题思路就是, 对于目标字符串target, 我们想要使用最少的代价进行拼词,

这道题如何想到用bfs主要就是对于一个字符串

target, 我们提供的每一个词都有对应的一种展开, 如下图

图片:
在这里插入图片描述

从上面的演示过程也不难看出, 我们这个本题剪枝的关键就是对target的进行排序操作, 主要就是优先削减头部的字符

代码实现如下(重点在理解逻辑)

class Solution {
    public static int minStickers(String[] stickers, String target) {
        //首先对数组中的单词排序并进行词频统计
        List<int[]> times = new ArrayList<>();
        for(int i = 0; i < stickers.length; i++){
            int[] temp = new int[26];
            String changeStr = sort(stickers[i], temp);
            stickers[i] = changeStr;
            times.add(temp);
        }
        //排序一下target字符串
        int[] targetTime = new int[26];
        target = sort(target, targetTime);
        Queue<String> q = new ArrayDeque<>();
        HashSet<String> set = new HashSet<>();
        StringBuilder sp = new StringBuilder();
        //进行bfs的主流程
        q.offer(target);
        int level = 0;
        //本质上还是我们弹出的逻辑没有搞懂, 我们应该一层一层的弹出
        while(!q.isEmpty()){
            int sz = q.size();
            level++;
            while(sz-- != 0){
                int[] curTime = new int[26];
                String cur = q.poll();
                //统计一下当前的词频
                for(int i = 0; i < cur.length(); i++){
                    curTime[cur.charAt(i) - 'a']++;
                }
                for(int i = 0; i < stickers.length; i++){
                    if(times.get(i)[cur.charAt(0) - 'a'] != 0){
                        String next = buildStr(curTime, times.get(i), sp);
                        if(next.equals("")) return level;
                        if(!set.contains(next)) {
                            set.add(next);
                            q.offer(next);
                        }
                    }
                }
            }
        }
        return -1;
    }

    //对字符串排序的方法, 顺便统计一下词频
    private static String sort(String s, int[] temp){
        char[] cs = s.toCharArray();
        for(char elem : cs){
            temp[elem - 'a']++;
        }
        Arrays.sort(cs);
        return String.valueOf(cs);
    }

    //生成一个新的字符串
    private static String buildStr(int[] curTime, int[] time, StringBuilder sp){
        sp.setLength(0);
        for(int i = 0; i < 26; i++){
            if(curTime[i] != 0){
                for(int j = 0; j < Math.max(curTime[i] - time[i], 0); j++){
                    sp.append((char)(i + 'a'));
                }
            }
        }
        return sp.toString();
    }

}

01bfs解析

基本过程

01bfs是一种特殊的bfs, 适用于01图找寻最短路径的情况, 01bfs时间复杂度是O(节点数量 + 边的数量) 下图是我们的实例

图片:
在这里插入图片描述

上面就是一个01bfs找寻最短路径的情况, 我们的解题的流程是固定的, 如下(正确性证明略), 主要就是双端队列结合bfs

  1. 创建一个distance表, 含义就是源点到i点的最短距离是多少

    大小就是所有的节点位置, 初始化所有点的distance[i] = Integer.MAX_VALUE

  2. 将源点加入双端队列, 并修改distance[源点] = 0

  3. 当队列不为空的时候进入循环(下面就是伪代码)

    while(!queue.isEmpty()){
        //弹出一个节点(弹出的时候一定从头部弹出)
        Node node = queue.poll();
        //如果这个位置就是要找的目标节点就直接返回
        if(node == targetNode) return distance[node];
        //找到这个节点去的下一个位置(可能有多个...)
        int next = node -> next;
        //weight就是这两个点之间的权值(0 or 1)
        int weight = 0 or 1;    
        if(distance[node] + weight < distance[next]){
            //此时说明到达next的位置可以边的更小就更新
            distance[next] = distance[node] + weight;
            //然后在队列中加入这个位置, 如果刚才的权值weight == 0, 就从头部加入, 如果是1就从尾部加入
            if(weight == 0){
                queue.offerFirst(node);
            }else{
                queue.offerLast(node);
            }
        }
    }
    

相关习题

图片: 在这里插入图片描述

链接: leetcode2290.到达角落的最小代价

其实就是01bfs的模板题

class Solution {
    //经典01dfs板子题
    private static final int[] move = new int[]{-1, 0, 1, 0, -1};

    public int minimumObstacles(int[][] grid) {
        int r = grid.length;
        int c = grid[0].length;
        //初始化一个distance数组
        int[][] distance = new int[r][c];
        for(int i = 0; i < r; i++){
            for(int j = 0; j < c; j++){
                distance[i][j] = Integer.MAX_VALUE;
            }
        }
        //创建一个双端队列
        Deque<int[]> dq = new ArrayDeque<>();
        dq.offer(new int[]{0, 0});
        distance[0][0] = 0;
        while(!dq.isEmpty()){
            int[] cur = dq.poll();
            //如果是目标节点
            if(cur[0] == r - 1 && cur[1] == c - 1) return distance[cur[0]][cur[1]];
            //尝试向四个方向扩展
            for(int k = 0; k < 4; k++){
                int nx = cur[0] + move[k];
                int ny = cur[1] + move[k + 1];
                if(nx >= 0 && nx < r && ny >= 0 && ny < c && distance[cur[0]][cur[1]] + grid[nx][ny] < distance[nx][ny]){
                    distance[nx][ny] = distance[cur[0]][cur[1]] + grid[nx][ny];
                    if(grid[nx][ny] == 0){
                        dq.offerFirst(new int[]{nx, ny});
                    }else{
                        dq.offerLast(new int[]{nx, ny});
                    }
                }
            }
        }
        return -1;
    }
rst(new int[]{nx, ny});
                    }else{
                        dq.offerLast(new int[]{nx, ny});
                    }
                }
            }
        }
        return -1;
    }
}

链接: leetcode1368.箭头数组的最短代价

图片: 在这里插入图片描述

class Solution {
    //这个move数组的设计是比较的精巧的
    private static final int[][] move = {{0}, {0, 1}, {0, -1}, {1, 0}, {-1, 0}};

    public int minCost(int[][] grid) {
        int r = grid.length;
        int c = grid[0].length;
        //初始化distance数组
        int[][] distance = new int[r][c];
        for(int i = 0; i < r; i++){
            Arrays.fill(distance[i], Integer.MAX_VALUE);
        }
        //创建双端队列
        Deque<int[]> dq = new ArrayDeque<>();
        dq.offer(new int[]{0, 0});
        distance[0][0] = 0;
        while(!dq.isEmpty()){
            int[] cur = dq.poll();
            int x = cur[0];
            int y = cur[1];
            if(x == r - 1 && y == c - 1) return distance[x][y];
            for(int i = 1; i < 5; i++){
                int nx = x + move[i][0];
                int ny = y + move[i][1];
                int weight = i == grid[x][y] ? 0 : 1;
                if(nx >= 0 && nx < r && ny >= 0 && ny < c && distance[x][y] + weight < distance[nx][ny]){
                    distance[nx][ny] = distance[x][y] + weight;
                    if(weight == 0){
                        dq.offerFirst(new int[]{nx, ny});
                    }else{
                        dq.offerLast(new int[]{nx, ny});
                    }
                }
            }
        }
        return -1;
    }
}

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

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

相关文章

高性能缓存方案 —— Caffeine

一、简介 Caffeine是一个高性能的Java缓存库&#xff0c;它提供了本地缓存的功能。 Caffeine和Redis都是内存级别的缓存&#xff0c;为什么要使用在这两缓存作为二级缓存&#xff0c;它们两有什么区别呢? 虽然它们都是内存级别的缓存&#xff0c;但是Redis是需要单独部署的&…

DAY42WEB 攻防-PHP 应用MYSQL 架构SQL 注入跨库查询文件读写权限操作

一、PHP-MYSQL-SQL注入-常规查询 1.PHP-MYSQL-Web组成架构 MySQL(统一管理) ​ root&#xff08;自带默认&#xff09; ​ 网站A testA ​ 网站B testB MySQL(一对一管理) ​ testA用户 ​ 网站A testA ​ testB用户 ​ 网站B testB access无数据库用户 mysql里面有内置的管理…

使用 Stata 调用本地部署的大语言模型进行文本主要内容提取——历年政府工作报告中的经济增长目标提取

因此今天给大家分享一个新的方法。也就是通过部署在本地的大模型进行文本内容提取。 安装 Ollama 通过 Ollama 可以快速在本地部署一些常用的大模型。可以根据自己的系统从这里安装下载&#xff1a;https://github.com/ollama/ollama MacOS 下载 Ollama 链接: https://ollama…

计算机的错误计算(一百一十八)

摘要 探讨一个不动点的计算精度问题。 不动点是一类特殊的循环迭代。它有形式 例1. 已知迭代[1] 计算 显然&#xff0c;每个 均为 0.5 . 不妨在Visual Studio 2010 下用下列C语言代码计算&#xff1a; #include <stdio.h> #include <math.h>int main() {do…

Mac上功能全面,免费好用的解压缩工具

在日常使用Mac的过程中&#xff0c;相信不少朋友都有解压缩需求&#xff0c;目前存在的解压缩软件可以说各种各样&#xff0c;但是有的收费&#xff0c;有的解压速度慢&#xff0c;有的解压类型不全&#xff0c;各有优缺点&#xff0c;挑选起来眼花缭乱&#xff0c;挑来挑去也没…

西安凭借入驻企业展示科技“硬”实力的数字媒体产业园

在古城西安的怀抱中&#xff0c;一座以科技“硬”实力为核心竞争力的数字媒体产业园——西安国际数字影像产业园&#xff0c;正以其独特的魅力和无限的潜力&#xff0c;吸引着全球的目光。这里&#xff0c;不仅是数字创意的孵化场&#xff0c;更是科技创新的策源地。 西安国际数…

STM32 HAL库

1. 相关概念 1.1. 回调函数 Callback()回调函数与普通函数的本质区别在于调用者不同&#xff1a;普通函数由用户代码调用&#xff0c;而回调函数则是由系统在适当的条件下调用。回调函数用于对各种事件的响应和处理&#xff0c;如当指定的EXTI线上发生中断或事件时&#xff0…

windows基于MediaPipe 和 TensorFlow.js的3D手势检测

目录 流程总结 第一步&#xff1a;安装 Node.js 和 Yarn 1.安装 Node.js&#xff1a; 1.1 如果安装了其他版本不合适&#xff0c;可以安装调整nvm-setup.exe来调整 2.安装 Yarn&#xff1a; 第二步&#xff1a;克隆项目仓库 第三步&#xff1a;替换共享文件 1.删除旧的…

Java基本数据类型和String类型的转换

1.基本介绍 在程序开发中&#xff0c;我们经常需要将基本数据类型转换成String类型。或者将String类型转为基本数据类型。 2.基本类型转String类型 语法&#xff1a;将 基本数据类型的值 “” 即可 3.String类型转基本数据类型 语法&#xff1a;通过基本类型的包装类调用…

Web3与传统互联网的比较:机遇与挑战

随着科技的不断进步&#xff0c;Web3作为新一代互联网的概念逐渐浮出水面&#xff0c;改变了我们对网络的认知。相较于传统互联网&#xff0c;Web3在许多方面展现出不同的特征与潜力。本文将对Web3与传统互联网进行比较&#xff0c;探讨其带来的机遇与挑战。 一、核心概念的差异…

MySQL启动失败解决方案

目录 引言 一、查看/启动mysql服务的两种方式 方法一&#xff1a; 方法二&#xff1a; 二、修改mysql服务启动路径的地址 三、"my.ini"文件的使用 设置my.ini文件的路径 给出一个使用my.ini文件的小例子 引言 造成启动闪退\失败的原因我仅仅以个人查询的一下博…

Note24100901_Portal_V18_Advanced5_Modscan的Modbus仿真

Note24100901_Portal_V18_Advanced5_Modscan 的Modbus TCP/IP仿真 具体过程详见如下图片&#xff1a;![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/8d85098268484044b2816d8a78c031f4.png#pic_center 以上~

搭建企业域名服务器案例

任务要求&#xff1a; 某企业要建立一台应用于以下情况的主域名服务器 拥有一个C类网段地址&#xff0c;为202.101.55.0。企业域名注册为company.com。域名服务器的IP地址定位为202.101.55.55&#xff0c;主机名为dns.company.com。企业网通过路由器与Internet连接。要解析的…

Ultralytics YOLO V11 初体验—训练COCO数据集—全流程记录

Utralytics YOLO V11&#x1f680; 之后做项目要用到YOLO&#xff0c;然后也是趁着这个机会捡一下YOLO的相关的知识&#xff0c;这篇博客记录一下&#xff0c;配置Utralytics建立一个训练YOLO的环境&#xff0c;然后使用官方提供的COCO.yaml文件进行自动下载和训练的完整过程&a…

springboot简单案例

必答[简答题]从页面输入年龄&#xff0c;输入的年龄在1-200之间为正常&#xff0c;其余定义为异常&#xff0c;并把年龄结果显示在页面上&#xff0c;同时给出判断(正常输出“年龄是XX岁“;若年龄大于200或小于0&#xff0c;输出自定义异常”年龄输入不正确“;其他显示异常情况…

从0到1:多服务厅预约小程序开发笔记(上)

需求调研 多服务厅预约小程序&#xff1a;随着信息技术的快速发展和移动互联网的普及&#xff0c;越来越多的服务行业开始向线上转型, 传统的预约方式往往效率低下&#xff0c;用户需耗费大量时间进行电话预约或现场排队&#xff0c;服务厅预约小程序集多种服务于一体&#xf…

嵌入式学习-线性表-Day04-队列

嵌入式学习-线性表-Day04-队列 队列 循环队列&#xff08;顺序队列&#xff09; 1&#xff09;创建一个空的队列 2&#xff09;入列 3&#xff09;求长度 链式队列 (1)创建一个空的队列 (2)入列 (3)出列 队列 1 什么是队列&#xff1f; 只允许在两端进行插入和删除操作的线性表…

深度学习——线性神经网络(二、线性回归的从零开始实现)

目录 2.1 生成数据集2.2 读取数据集2.3 初始化模型参数2.4 定义模型2.5 定义损失函数2.6 定义优化算法2.7 训练 2.1 生成数据集 为简单展示&#xff0c;将根据带有噪声的线性模型构造一个数据集。生成一个包含1000个样本的数据集。每个样本包含从标准正态分布中的抽样的两个特征…

RocketMq-秒杀应用场景

1、介绍mq 2、秒杀介绍 -----redis配置 3、生产者-消费者搭建 1、介绍mq 消息存储架构图中主要有下面三个跟消息存储相关的文件构成。 (1) CommitLog&#xff1a;消息主体以及元数据的存储主体&#xff0c;存储Producer端写入的消息主体内容,消息内容不是定长的。单个文件大…

微服务实战——登录(普通登录、社交登录、SSO单点登录)

登录 1.1. 用户密码 PostMapping("/login")public String login(UserLoginVo vo, RedirectAttributes redirectAttributes, HttpSession session){R r memberFeignService.login(vo);if(r.getCode() 0){MemberRespVo data r.getData("data", new Type…