搜索算法(四) 广度优先搜素算法

news2024/12/20 14:45:50

一、BFS

bfs一层一层地遍历图或树,一般用队列实现,可以计算距离目标的步数。

 

二、例题

1)

力扣icon-default.png?t=N4P3https://leetcode.cn/problems/shortest-bridge/ 这道题实际是计算两个岛屿之间的最短距离,可以先用dfs搜索到第一个岛屿并且记录第一个岛屿的每对坐标,接着以这些坐标作为bfs的起始节点集合,一层一层地向外遍历,寻找第二个岛屿,当找到第二个岛屿时,当前遍历的层数减一就是两个岛屿之间的最短路径。

tips: 将第一个岛屿的坐标值标为2,方便区分两个岛屿

       bfs遍历过程中,将0标为2,表示已加入了第一个岛屿

class Solution {
public:
    int shortestBridge(vector<vector<int>>& grid) {
        int n = grid.size();

        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]==1){
                    dfs(grid,i,j);
                    return bfs(grid);
                }
            }
        }
        return 0;
    }

    int bfs(vector<vector<int>>& grid){
        int level = 0;
        int m;
        int n = grid.size();
        while(!first.empty()){
            level++;
            m = first.size();
            while(m--){
                auto[a,b] = first.front();
                first.pop();
                for(int i=0;i<4;i++){
                    int x = a + path[i];
                int y = b + path[i+1];
                if(x>=0 && y>=0 && x<n && y<n){
                    if(grid[x][y]==0){
                        grid[x][y] = 2;
                        first.push({x,y});
                    }else if(grid[x][y]==1){
                        return level-1;
                    }
                    
                    }
                }
                
            }
        }
        return 0;
    }

    void dfs(vector<vector<int>>& grid, int a, int b){
        grid[a][b] = 2;
        first.push({a,b});
        int n = grid.size();
        for(int i=0;i<4;i++){
            int x = a + path[i];
            int y = b + path[i+1];
            if(x>=0 && y>=0 && x<n && y<n && grid[x][y]==1){
                dfs(grid, x, y);
            }
        }
    }

private:
    queue<pair<int,int>> first;
    vector<int> path{-1,0,1,0,-1};
};

2)

力扣icon-default.png?t=N4P3https://leetcode.cn/problems/word-ladder-ii/单词接龙可以理解成寻找两个单词之间的最短路径,当两个单词之间只有一个字母不相同时可以认为两个单词之间有一条双向边,这道题就是寻找起始单词到目标单词的最短路径。

用BFS从起始单词开始,变换一个字母,代表走了一步,变换两个字母,代表走了两步了,一层一层变换,一直遍历到达目标单词。

在一层一层遍历的过程中,需要记录下每一层变换所得到的单词,这样BFS结束后,可以用DFS找到最短路径。

我一开始用的是双向BFS+DFS,基于正向的逻辑,在第34个测试用例时超时了。

class Solution {
public:
    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
        vector<vector<string>> ans;
        unordered_set<string> dict;
        for(auto w:wordList){
            dict.insert(w);
        }
        if(dict.count(endWord)==0){
            return ans;
        }
        dict.erase(beginWord);
        dict.erase(endWord);
        unordered_set<string> q1{beginWord}, q2{endWord};
        unordered_map<string,vector<string>> next;
        bool reversed = false, found = false;
        while(!q1.empty()){
            unordered_set<string> q;
            for(const auto& w:q1){
                string s = w;
                for(int i=0;i<s.size();i++){
                    char ch = s[i];
                    for(int j=0;j<26;j++){
                        s[i] = 'a' + j;
                        if(q2.count(s)){
                            reversed?next[s].push_back(w):next[w].push_back(s);
                            found = true;
                        }
                        if(dict.count(s)){
                            reversed?next[s].push_back(w):next[w].push_back(s);
                            q.insert(s);
                        }
                    }
                    s[i] = ch;
                }
            }
                if(found){
                    break;
                }
                for(const auto& w:q){
                    dict.erase(w);
                }
                if(q.size()<q2.size()){
                    q1 = q;
                }else{
                    reversed = !reversed;
                    q1 = q2;
                    q2 = q;
                }
            }
            if(found){
                vector<string> path;
                path.push_back(beginWord);
                dfs(beginWord,endWord,next,path,ans);
                
            }
            return ans;
    }
    void dfs(string beginWord, string endWord, unordered_map<string,vector<string>>& next, vector<string>& path, vector<vector<string>>& ans ){
        if(beginWord==endWord){
            ans.push_back(path);
        }
        for(const auto& w:next[beginWord]){
            path.push_back(w);
            dfs(w,endWord,next,path,ans);
            path.pop_back();
        }
    }
};

结果:

 

看了一下大家的讨论,发现错误在于DFS搜索是正向搜索的(从起始单词向目标单词搜索),这样会导致指数级别的可能路径。

为了不超时,正确做法是从目标单词反向向起始单词搜索,这样可能路径会少很多。

将前面的代码简单修改:记录每一层原单词变换所得到的所有单词,改为记录单词可由哪些单词得到。DFS从目标单词开始搜索,反转最终路径。

class Solution {
public:
    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
        vector<vector<string>> ans;
        unordered_set<string> dict;
        for(auto w:wordList){
            dict.insert(w);
        }
        if(dict.count(endWord)==0){
            return ans;
        }
        dict.erase(beginWord);
        dict.erase(endWord);
        unordered_set<string> q1{beginWord}, q2{endWord};
        unordered_map<string,vector<string>> next;
        bool reversed = false, found = false;
        while(!q1.empty()){
            unordered_set<string> q;
            for(const auto& w:q1){
                string s = w;
                for(int i=0;i<s.size();i++){
                    char ch = s[i];
                    for(int j=0;j<26;j++){
                        s[i] = 'a' + j;
                        if(q2.count(s)){
                            reversed?next[w].push_back(s):next[s].push_back(w);
                            found = true;
                        }
                        if(dict.count(s)){
                            reversed?next[w].push_back(s):next[s].push_back(w);
                            q.insert(s);
                        }
                    }
                    s[i] = ch;
                }
            }
                if(found){
                    break;
                }
                for(const auto& w:q){
                    dict.erase(w);
                }
                if(q.size()<q2.size()){
                    q1 = q;
                }else{
                    reversed = !reversed;
                    q1 = q2;
                    q2 = q;
                }
            }
            if(found){
                vector<string> path;
                path.push_back(endWord);
                dfs(endWord, beginWord,next,path,ans);
                
            }
            return ans;
    }
    void dfs(string beginWord, string endWord, unordered_map<string,vector<string>>& next, vector<string>& path, vector<vector<string>>& ans ){
        if(beginWord==endWord){
            reverse(path.begin(), path.end());
            ans.push_back(path);
            reverse(path.begin(), path.end());
        }
        for(const auto& w:next[beginWord]){
            path.push_back(w);
            dfs(w,endWord,next,path,ans);
            path.pop_back();
        }
    }
};

结果:

 这个结果不算好,我就把双向BFS改成单向了,执行时间明显变快了,说明双向BFS属实没必要。

class Solution {
public:
    vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
        vector<vector<string>> ans;
        unordered_set<string> dict;
        for(auto w:wordList){
            dict.insert(w);
        }
        if(dict.count(endWord)==0){
            return ans;
        }
        dict.erase(beginWord);
       
        unordered_set<string> q1{beginWord};
        unordered_map<string,vector<string>> next;
        bool found = false;
        while(!q1.empty()){
            unordered_set<string> q;
            for(const auto& w:q1){
                string s = w;
                for(int i=0;i<s.size();i++){
                    char ch = s[i];
                    for(int j=0;j<26;j++){
                        s[i] = 'a' + j;
                        if(s==endWord){ 
                            next[s].push_back(w);
                            found = true;
                            break;
                        }
                        if(dict.count(s)){
                            next[s].push_back(w);
                            q.insert(s);
                        }
                    }
                    
                    s[i] = ch;
                }
            }
                if(found){
                    break;
                }
                for(const auto& w:q){
                    dict.erase(w);
                }
                q1 = q;
                
            }
            if(found){
                vector<string> path;
                path.push_back(endWord);
                dfs(endWord, beginWord,next,path,ans);
                
            }
            return ans;
    }
    void dfs(string beginWord, string endWord, unordered_map<string,vector<string>>& next, vector<string>& path, vector<vector<string>>& ans ){
        if(beginWord==endWord){
            reverse(path.begin(), path.end());
            ans.push_back(path);
            reverse(path.begin(), path.end());
        }
        for(const auto& w:next[beginWord]){
            path.push_back(w);
            cout<<"*push****        "<<w<<endl;
            dfs(w,endWord,next,path,ans);
            cout<<"-pop-----        "<<w<<endl;
            path.pop_back();
        }
    }
};

 

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

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

相关文章

TDEngine - taosdump的安装与使用实战

taosdump的安装与使用实战 一、taosdump简介二、下载三、安装四、taosdump主要参数五、taosdump数据导出&#xff08;备份&#xff09;六、taosdump数据导入七、不同版本的数据迁移7.1 问题&#xff1a;报错- create database 语句不一致7.2 解决&#xff1a;修改导出的dbs.sql…

MTK平台的SWT异常的简单总结(2)——SWT原理和分析

&#xff08;1&#xff09;原理性 &#xff08;2&#xff09;SWT如何抓取Log 遇到SWT问题详细可参考MTK提供的FAQ&#xff1a;SWT机制介绍。 获取Ap Log的路径&#xff1a;/sdcard/debuglogger/mobilelog/APLog_XXXXX 获取db的路径&#xff1a;/data/aee_exp 如果db没有打包…

RK3288 Android5.1添加WiFiBT模块AP6212

CPU&#xff1a;RK3288 系统&#xff1a;Android 5.1 注&#xff1a;RK3288系统&#xff0c;目前 Android 5.0 Kernel 3.10 SDK 支持 Braodcom,Realtek 等 WiFi BT 模块 各个 WiFi BT 模块已经做到动态兼容&#xff0c;Android 上层不再需要像以前一样进 行特定宏的配置 此…

华为OD机试真题 Java 实现【关联子串】【2023Q1 100分】,附详细解题思路

一、题目描述 给定两个字符串str1和str2&#xff0c; str1进行排列组合只要有一个为str2的子串则认为str1是str2的关联子串&#xff0c; 请返回子串在str2的起始位置&#xff0c;若不是关联子串则返回-1。 二、输入描述 qwe dsgfasgfwe 三、输出描述 -1 四、解题思路 …

遇到大数据处理,你会怎么办?快来看一下位图和布隆过滤器(下)

目录 前文 一&#xff0c;为什么有布隆过滤器 二&#xff0c;什么是布隆过滤器 三&#xff0c;布隆过滤器的实现 四&#xff0c;布隆过滤器的优缺点 4.1 布隆过滤器的优点 4.2 布隆过滤器的缺点及其改进方式 4.2.1 查找误判及其改进方式分析 4.2.2 不能删除以及改进方式分…

【HTML】第 1 节 - HTML 初体验

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 。 时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 目录 1、缘起 2、HTML 概念 2.1、HTML 定义 2.2、标签语法 3、HTML 基本骨架 4、标签的关系 5、注释 6、总结 1、缘起 最近在学习微信小程…

程序员0基础转行大数据年薪25万,只因我做了这件事...

现在我在成都的一家企业做大数据架构师&#xff0c;一个月税前可以拿到20k&#xff0c;还有项目奖金&#xff0c;一年下来最少也能拿25万。生活和工作也都在有条不紊地运转&#xff0c;每天也会有新的挑战&#xff0c;这正是我想要的生活。 01 机械工程专业 但我决定转行互联…

SpringBoot自定义starter之接口日志输出

文章目录 前言文章主体1 项目全部源码2 项目结构介绍3 starter 的使用3.1 配置文件 application,yml的内容3.2 启动类3.3 控制器类 4 测试结果 结语 前言 本文灵感来源是一道面试题。 要求做一个可以复用的接口日志输出工具&#xff0c;在使用时引入依赖&#xff0c;即可使用。…

MySQL数据库 10.DCL操作

目录 &#x1f914; 前言&#xff1a; &#x1f914;DCL介绍&#xff1a; &#x1f914;1.DCL管理用户&#xff1a; 1.查询用户&#xff1a; 图示&#xff1a; 2.创建用户 示例1&#xff1a; 运行结果&#xff1a;​ 示例2&#xff1a; 运行结果&#xff1a;​ 3.修改…

算法修炼之筑基篇——筑基一层中期(解决01背包,完全背包,多重背包)

✨博主&#xff1a;命运之光​​​​​​ &#x1f984;专栏&#xff1a;算法修炼之练气篇​​​​​ &#x1f353;专栏&#xff1a;算法修炼之筑基篇 ✨博主的其他文章&#xff1a;点击进入博主的主页​​​​​​ 前言&#xff1a;学习了算法修炼之练气篇想必各位蒟蒻们的基…

安全——网络安全协议的引入

TCP/IP安全缺陷 信息泄露 概述 网络中投递的报文往往包含账号、口令等敏感信息&#xff0c;若这些信息泄露则是灾难性的后果。其中嗅探是一种常见而隐蔽的网络攻击手段。 嗅探 概述 问题&#xff1a;在共享式网络架构下&#xff0c;所有的数据都是以广播方式进行发送&…

程序员大专毕业,月薪2w是什么体验?

在这个数据驱动的时代&#xff0c;大数据行业的发展前景也非常广阔&#xff0c;我相信我的未来会越来越光明 01 开始学习 是迈向前方的第一步 我是三月&#xff0c;一个来自小城市的大专毕业生。现在在杭州一家公司做大数据开发工程师&#xff0c;目前薪资是20k*13。 我本身…

运维小白必学篇之基础篇第十三集:网络概述中继实验

网络概述中继实验 实验作业&#xff08;主机名为自己的名字&#xff09;&#xff1a; 1、搭建中继环境&#xff0c;要求如下&#xff1a; 网络要求&#xff1a; 内网&#xff1a;192.168.50.50 网关&#xff1a;192.168.50.254 192.168.60.254 外网&#xff1a;192.168.60.60 主…

【论文阅读】An Object SLAM Framework for Association, Mapping, and High-Level Tasks

一、系统概述 这篇文章是一个十分完整的物体级SLAM框架&#xff0c;偏重于建图及高层应用&#xff0c;在前端的部分使用了ORBSLAM作为基础框架&#xff0c;用于提供点云以及相机的位姿&#xff0c;需要注意的是&#xff0c;这篇文章使用的是相机&#xff0c;虽然用的是点云这个…

DevOps该怎么做?

年初在家待了一段时间看了两本书收获还是挺多的. 这些年一直忙于项目, 经历了软件项目的每个阶段, 多多少少知道每个阶段是个什么, 会做哪些事情浮于表面, 没有深入去思考每个阶段背后的理论基础, 最佳实践和落地工具. 某天leader说你书看完了, 只有笔记没有总结, 你就写个总结…

小白必看!轻松理解和解决MySQL幻读问题!

大家好&#xff0c;我是小米&#xff01;今天我来给大家分享一下关于MySQL数据库中常见的一个问题——幻读&#xff0c;以及如何解决它。相信对于数据库开发和管理的小伙伴们来说&#xff0c;幻读是一个相对棘手的问题&#xff0c;但只要我们掌握了正确的解决方法&#xff0c;它…

网络故障管理

网络故障管理是以最快的方式查找、隔离和排除网络故障的过程。故障管理是网络管理的重要组成部分&#xff0c;它通过快速解决故障来最大限度地减少停机时间并防止设备故障&#xff0c;从而确保最佳的网络可用性并防止业务损失。 网络故障监控是故障管理的第一步&#xff0c;因…

Linux Shell脚本攻略

一、echo命令 1、在echo中转义换行符 默认情况下&#xff0c;echo会在输出文本的尾部追加一个换行符。可以使用选项-n来禁止这种行为。 echo同样接受双包含转义序列的双引号字符串作为参数。在使用转义序列时&#xff0c;需要使用echo -e "包含转义序列的字符串"这…

有哪些测试框架和工具推荐? - 易智编译EaseEditing

在软件测试领域&#xff0c;有许多测试框架和工具可供选择。以下是一些常见的测试框架和工具的推荐&#xff1a; Selenium: 一个用于自动化Web应用程序测试的流行框架。它支持多种编程语言&#xff0c;并提供丰富的功能和灵活性。 JUnit: 一个用于Java应用程序的单元测试框架…

MongoDB(学习笔记1.0)

最近在学非关系型数据库MongoDB&#xff0c;猛地用起来的真的没关系型数据库方便啊。 首先还是数据库的安装&#xff1a; 安装直接去官网安装即可&#xff0c;官网地址&#xff1a;MongoDB: The Developer Data Platform | MongoDB 当前也有免安装版的&#xff0c;这里就不再…