BFS算法专题

news2024/11/28 6:41:44

BFS算法专题

    • 框架篇
    • LeetCode 111. 二叉树的最小深度
      • 解题思路
      • 代码实现
    • LeetCode 752. 打开转盘锁
      • 解题思路
      • 代码实现
    • LeetCode 773. 滑动谜题
      • 解题思路
      • 代码实现
    • 总结

不要纠结,干就完事了,熟练度很重要!!!多练习,多总结!!!

框架篇

BFS 问题的本质就是让你在一幅「图」中找到从起点start到终点target的最近距离,这个例子听起来很枯燥,但是 BFS 算法问题其实都是在干这个事儿。

// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
    Queue<Node> q; // 核心数据结构
    Set<Node> visited; // 避免走回头路

    q.offer(start); // 将起点加入队列
    visited.add(start);
    int step = 0; // 记录扩散的步数

    while (q not empty) {
        int sz = q.size();
        /* 将当前队列中的所有节点向四周扩散 */
        for (int i = 0; i < sz; i++) {
            Node cur = q.poll();
            /* 划重点:这里判断是否到达终点 */
            if (cur is target)
                return step;
            /* 将 cur 的相邻节点加入队列 */
            for (Node x : cur.adj())
                if (x not in visited) {
                    q.offer(x);
                    visited.add(x);
                }
        }
        /* 划重点:更新步数在这里 */
        step++;
    }
}

队列q就不说了,BFS 的核心数据结构;cur.adj()泛指cur相邻的节点,比如说二维数组中,cur上下左右四面的位置就是相邻节点;visited的主要作用是防止走回头路,大部分时候都是必须的,但是像一般的二叉树结构,没有子节点到父节点的指针,不会走回头路就不需要visited。

  1. 为什么 BFS 可以找到最短距离,DFS 不行吗?

首先,你看 BFS 的逻辑,depth每增加一次,队列中的所有节点都向前迈一步,这保证了第一次到达终点的时候,走的步数是最少的。

DFS 不能找最短路径吗?其实也是可以的,但是时间复杂度相对高很多。

DFS 实际上是靠递归的堆栈记录走过的路径,你要找到最短路径,肯定得把二叉树中所有树杈都探索完才能对比出最短的路径有多长对不对

BFS 借助队列做到一次一步「齐头并进」,是可以在不遍历完整棵树的条件下找到最短距离的。

形象点说,DFS 是线,BFS 是面;DFS 是单打独斗,BFS 是集体行动。这个应该比较容易理解吧。

  1. 既然 BFS 那么好,为啥 DFS 还要存在?

BFS 可以找到最短距离,但是空间复杂度高,而 DFS 的空间复杂度较低。

拿《LeetCode 111. 二叉树的最小深度》举例,假设给你的这个二叉树是满二叉树,节点总数为N,对于 DFS 算法来说,空间复杂度无非就是递归堆栈,最坏情况下顶多就是树的高度,也就是O(logN)。

BFS 算法,队列中每次都会储存着二叉树一层的节点,这样的话最坏情况下空间复杂度应该是树的最底层节点的数量,也就是N/2,用 Big O 表示的话也就是O(N)

BFS 还是有代价的,一般来说在找最短路径的时候使用 BFS,其他时候还是 DFS 使用得多一些(主要是递归代码好写)

LeetCode 111. 二叉树的最小深度

在这里插入图片描述

解题思路

题目要求求出最小高度,BFS通过队列可以做到从根节点往下“齐头并进”进行逐行扫描,那么终止条件是什么呢?走到叶子结点即该节点的左右子树均为空,便可以返回。

代码实现

class Solution {
    public int minDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        int dep = 1;
        while(!queue.isEmpty()){
            int size = queue.size();
            for(int i = 0; i < size;i++){
                TreeNode cur = queue.poll();
                if(cur.left == null && cur.right == null){
                    return dep;
                }
                if(cur.left != null){
                    queue.add(cur.left);
                }
                if(cur.right != null){
                    queue.add(cur.right);
                }
            }
            dep++;
        }
        return dep;
    }
}

LeetCode 752. 打开转盘锁

在这里插入图片描述

解题思路

我们不管所有的限制条件,不管deadends和target的限制,就思考一个问题:如果让你设计一个算法,穷举所有可能的密码组合,你怎么做?

// 将 s[j] 向上拨动一次
String plusOne(String s, int j) {
    char[] ch = s.toCharArray();
    if (ch[j] == '9')
        ch[j] = '0';
    else
        ch[j] += 1;
    return new String(ch);
}
// 将 s[i] 向下拨动一次
String minusOne(String s, int j) {
    char[] ch = s.toCharArray();
    if (ch[j] == '0')
        ch[j] = '9';
    else
        ch[j] -= 1;
    return new String(ch);
}

// BFS 框架,打印出所有可能的密码
void BFS(String target) {
    Queue<String> q = new LinkedList<>();
    q.offer("0000");

    while (!q.isEmpty()) {
        int sz = q.size();
        /* 将当前队列中的所有节点向周围扩散 */
        for (int i = 0; i < sz; i++) {
            String cur = q.poll();
            /* 判断是否到达终点 */
            System.out.println(cur);

            /* 将一个节点的相邻节点加入队列 */
            for (int j = 0; j < 4; j++) {
                String up = plusOne(cur, j);
                String down = minusOne(cur, j);
                q.offer(up);
                q.offer(down);
            }
        }
        /* 在这里增加步数 */
    }
    return;
}

这段 BFS 代码已经能够穷举所有可能的密码组合了,但是显然不能完成题目,有如下问题需要解决:

  1. 会走回头路。比如说我们从"0000"拨到"1000",但是等从队列拿出"1000"时,还会拨出一个"0000",这样的话会产生死循环。
  2. 没有终止条件,按照题目要求,我们找到target就应该结束并返回拨动的次数。
  3. 没有对deadends的处理,按道理这些「死亡密码」是不能出现的,也就是说你遇到这些密码的时候需要跳过。

代码实现

class Solution {
    public int openLock(String[] deadends, String target) {
        Set<String> dead = new HashSet<>();
        for(String s:deadends){
            dead.add(s);
        }
        Set<String> visit = new HashSet<>();
        Queue<String> queue = new LinkedList<>();
        queue.offer("0000");
        visit.add("0000");
        int step = 0;
        
        while(!queue.isEmpty()){
            int size = queue.size();
            for(int i = 0;i < size;i++){
                String cur = queue.poll();
                if(dead.contains(cur)){
                    continue;
                }
                if(cur.equals(target)){
                    return step;
                }
                for(int j=0;j < 4;j++){
                    String up = plusOne(cur, j);
                    if(!visit.contains(up)){
                        queue.offer(up);
                        visit.add(up);
                    }
                    String down = deOne(cur, j);
                    if(!visit.contains(down)){
                        queue.offer(down);
                        visit.add(down);
                    }
                }
            }
            step++;
        }
        return -1;
    }

    public String plusOne(String cur, int i){
        char[] ch = cur.toCharArray();
        if(ch[i] == '9'){
            ch[i] = '0';
        }else{
            ch[i]+=1;
        }
        return new String(ch);
    }

    public String deOne(String cur, int i){
        char[] ch = cur.toCharArray();
        if(ch[i] == '0'){
            ch[i] = '9';
        }else{
            ch[i]-=1;
        }
        return new String(ch);
    }
}

LeetCode 773. 滑动谜题

在这里插入图片描述

解题思路

在这里插入图片描述
其中比较有技巧性的点在于,二维数组有「上下左右」的概念,压缩成一维后,如何得到某一个索引上下左右的索引?

vector<vector<int>> neighbor = {
    { 1, 3 },
    { 0, 4, 2 },
    { 1, 5 },
    { 0, 4 },
    { 3, 1, 5 },
    { 4, 2 }
};

这个含义就是,在一维字符串中,索引i在二维数组中的的相邻索引为neighbor[i]

代码实现

class Solution {
    int[][] neighbors = {{1, 3}, {0, 2, 4}, {1, 5}, {0, 4}, {1, 3, 5}, {2, 4}};
    public int slidingPuzzle(int[][] board) {
        Queue<String> queue = new LinkedList<>();
        Set<String> visit = new HashSet<>();
        int step = 0;
        StringBuilder sb = new StringBuilder();
        for(int i = 0;i < 2;i++){
            for(int j = 0;j < 3;j++){
                sb.append(board[i][j]);
            }
        }
        queue.offer(sb.toString());
        visit.add(sb.toString());
        String target = "123450";

        while(!queue.isEmpty()){
            int size = queue.size();
            for(int i = 0; i < size;i++){
                String cur = queue.poll();
                if(cur.equals(target)){
                    return step;
                }

                int idx = cur.indexOf('0');

                for(int adj : neighbors[idx]){
                    String ready = cur;
                    String swapStr = swap(ready, idx, adj);
                    if(!visit.contains(swapStr)){
                        queue.offer(swapStr);
                        visit.add(swapStr);
                    }
                }

            }
            step++;
        }
        return -1;
    }

    public String swap(String cur, int i, int j){
        char[] ch = cur.toCharArray();
        char tmp = ch[i];
        ch[i] = ch[j];
        ch[j] = tmp;
        return new String(ch);
    }
}

总结

本题来源于Leetcode中 归属于BFS算法类型题目。
同许多在算法道路上不断前行的人一样,不断练习,修炼自己!
如有博客中存在的疑问或者建议,可以在下方留言一起交流,感谢各位!

觉得本博客有用的客官,可以给个点赞+收藏哦! 嘿嘿

喜欢本系列博客的可以关注下,以后除了会继续更新面试手撕代码文章外,还会出其他系列的文章!

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

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

相关文章

Vivado约束添加方法:一文全面解析IO和时序约束

FPGA开发离不开IO约束和时序约束&#xff0c;IO约束用于确定输入/输出端口的物理端口和电气特性&#xff0c;与芯片和电路设计有关。而时序约束则用于设定FPGA设计中的时序特性&#xff0c;以确保系统能够在预期时钟频率下正常运行。本文将介绍vivado中常见的设置约束的方法。 …

mysql 如何避免索引失效

案例演示 建表及初始化数据 CREATE TABLE staffs (id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(24) NOT NULL DEFAULT ,age INT NOT NULL DEFAULT 0,pos VARCHAR(20) NOT NULL DEFAULT ,#职位add_time TIMESTAMP NOT NULL DEFAULT CURREN…

二维码在设备点维一体化管理中的应用

随着科技发展&#xff0c;设备点维一体化管理体系应运而生&#xff0c;该管理体系的出现让设备维护保养变得更加高效精细化。 设备点维一体化管理体系以设备点检和维护保养为基础&#xff0c;通过日常、专业及精密点检&#xff0c;对点检测得的数据和设备给油脂保养情况进行统…

一个开源、免费在线数据建模、元数据管理平台,简单易用

一、开源项目简介 ERD Online 是一个开源、免费在线数据建模、元数据管理平台。提供简单易用的元数据设计、关系图设计、SQL查询等功能&#xff0c;辅以版本、导入、导出、数据源、SQL解析、审计、团队协作等功能、方便我们快速、安全的管理数据库中的元数据。 二、开源协议 …

全国各省份影像下载地址(11级别)

安徽省https://pan.baidu.com/s/1fMuWhVZFvSH1UlCGU1bPpA?pwdeasy澳门特别行政区https://pan.baidu.com/s/1aU2D4o2bfeHTJTb6AkUtVA?pwdeasy北京市https://pan.baidu.com/s/1eaNzAWm1pUx_rjhD_wHHhA?pwdeasy福建省百度网盘 请输入提取码甘肃省https://pan.baidu.com/s/1mAqf…

【Tools系列】IDA远程调试Linux文件

Date: 2023.4.28 文章目录 1、工具安装2、IDA+linux_server参考1、工具安装 IDA Pro 7.2 2、IDA+linux_server (1)进入到IDA的安装目录,找到文件夹/dbgsrv,其中有两个文件linux_server和linux_server64,分别为32位的服务端和64位的服务端,可根据调试目标进行选择。并将…

RocketMQ第二节(安装和模块详解)

目录 1&#xff1a;RocketMQ安装 1.1&#xff1a;下载安装包解压 1.2&#xff1a;修改运行配置 1.3&#xff1a;运行RocketMQ 1.3.1&#xff1a;启动NameServer 1.3.2&#xff1a;启动Broker 1.4&#xff1a;消息收发 1.5&#xff1a;服务关闭 2&#xff1a;RocketMQ模…

八部门联合推动IPv6创新发展 知道创宇助力IPv6快速安全改造

近日&#xff0c;工业和信息化部、中央网信办、国家发展改革委、教育部、交通运输部、人民银行、国务院国资委、国家能源局等八部门联合印发《关于推进IPv6技术演进和应用创新发展的实施意见》&#xff08;以下简称“《实施意见》”&#xff09;&#xff0c;提出到2025年底&…

换个花样玩C++(3)const_cast不是一行代码这么简单

我先不说const_cast的事情,还是回到我们之前遇到的一个问题,先看代码 示例1 #include <iostream> int main() {const int a = 1;int* p = (int*)(&a);*p = 100; std::cout << a; } 这段代码里,本来想通过指针p里修改a的值,vs调试看下来a的值在*p=100;这…

Elasticsearch --- 简介、安装

一、简介 1.1、elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 例如&#xff1a; 在GitHub搜索代码 在电商网站搜索商品 在百度搜索答案 1.2、ELK技术栈 elas…

Linux安装helm

前言 运行环境&#xff1a;CentOS7.9 官方参考文档&#xff1a;官方文档 文章末尾附有一键安装脚本 下载安装包 github下载对应版本的安装包&#xff0c;下载地址 进入对应版本的下载页面&#xff0c;这里以v3.11.3为例 选择对应系统的安装包&#xff0c;这里以linux为例 …

WhatsApp CRM:通过 CRM WhatsApp 集成向客户发送消息

WhatsApp CRM&#xff1a;通过 CRM WhatsApp 集成向客户发送消息 你是否在寻找一个支持WhatsApp整合的CRM&#xff1f;或者&#xff0c;你想将WhatsApp与你当前的CRM整合&#xff1f;这篇文章将回答你所有的问题。我们将首先了解什么是WhatsApp CRM&#xff0c;以及你需要知道…

【Python】丘比特之箭,一箭穿心,快去发给你心仪的人叭~

作者主页&#xff1a;爱笑的男孩。的博客_CSDN博客-深度学习,活动,YOLO领域博主爱笑的男孩。擅长深度学习,活动,YOLO,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域.https://blog.csdn.net/Code_and516?typeblog个人简…

20230428 -栈与队列1 | 232. 用栈实现队列、225. 用队列实现栈、Queue和Deque异同介绍

1、232. 用栈实现队列 class MyQueue {//整体实现思路&#xff1a;队列是先进先出&#xff0c;栈是先进后出。使用两个栈&#xff0c;一个栈A负责进&#xff0c;一个栈B负责接收栈A数据&#xff0c;然后出出&#xff0c;队列&#xff1a;A->B>C 栈A&#xff1a;A->B&g…

一分钟教你玩转组合图表

在日常工作中&#xff0c;有时候单一的图表类型无法满足多维度的数据展示&#xff0c;这时候就要考虑使用组合图表。 什么是组合图表呢&#xff1f; 就是将两种及两种以上的图表类型组合起来绘制在一个图表上。 下面我们通过经典的柱线组合图来手把手教会你如何制作组合图表。 …

ChatGLM LoRA微调实战方案

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

Kestrel封装在Winform中

Kestrel封装在Winform中 背景思路方法1方法2方法3&#xff08;本文使用的方法&#xff09; 实现在winform程序中引入几个nuget包新建一个Startup类&#xff08;叫什么名字都行&#xff09;修改Program文件创建controller 运行效果(打开浏览器&#xff0c;输入如下地址&#xff…

【高危】Apache Superset <2.1.0 认证绕过漏洞(POC)(CVE-2023-27524)

漏洞描述 Apache Superset 是一个开源的数据可视化和业务智能平台&#xff0c;可用于数据探索分析和数据可视化。 Apache Superset 受影响版本在使用默认的secret_key时&#xff0c;攻击者可通过默认的secret_key为任意用户生成有效的会话令牌&#xff0c;进而绕过验证造成信…

千耘导航让普通棉农享受到科技红利

孟师傅&#xff0c;新疆阿克苏一名普通的棉花种植户&#xff0c;从事农业20年&#xff0c;开拖拉机也有10多年&#xff0c;之前听过农机自动驾驶&#xff0c;但由于这里通信网络信号不太好&#xff0c;身边朋友使用农机导航效果不是特别理想&#xff0c;因此一直没享受到科技带…

【Python】Python学习笔记(六)字符串

字符串 基本操作 使用索引来读取字符串 Python允许使用正数或者负数来读取字符串中的某一个字符。 使用正数索引&#xff1a; #按顺序打印输出字符串内的所有字符。s ABCDEFGprint(s[0] , s[0]) print(s[1] , s[1]) print(s[2] , s[2]) print(s[3] , s[3]) print(s[4]…