Python - 深夜数据结构与算法之 Two-Ended BFS

news2024/12/23 6:20:49

目录

一.引言

二.双向 BFS 简介

1.双向遍历示例

2.搜索模版回顾

三.经典算法实战

1.Word-Ladder [127]

2.Min-Gen-Mutation [433]

四.总结


一.引言

DFS、BFS 是常见的初级搜索方式,为了提高搜索效率,衍生了剪枝、双向 BFS 以及 A* 即启发式搜索等高级搜索方式。剪枝通过避免不必要或者次优解来减少搜索的次数,提高搜索效率;双向 BFS 通过层序遍历从首尾逼近答案,提高搜索效率;启发式搜索则是从优先级的角度出发,基于优先级高低搜索,提高搜索效率。本文主要介绍双向 BFS 的使用。

二.双向 BFS 简介

1.双向遍历示例

◆  双向连通图

求 A -> L 所需最短路径。

◆  遍历层级关系

不同颜色代表不同层级的 BFS,绿色为 root,蓝色为第二层,从左向右递推。

◆  双向遍历

从 A/L 同时层序遍历,当二者扩散的点重合时,左右路径长度相加即为最短路径。

2.搜索模版回顾

◆ DFS - 递归

◆ DFS - 非递归 

◆ BFS - 栈 

三.经典算法实战

1.Word-Ladder [127]

单词接龙: https://leetcode.cn/problems/word-ladder/description/

◆ 单向 BFS

class Solution:    
    def ladderLength(self, beginWord, endWord, wordList):
        """
        :type beginWord: str
        :type endWord: str
        :type wordList: List[str]
        :rtype: int
        """
        valid_word = set(wordList)

        if endWord not in valid_word:
            return 0

        stack = [(beginWord, 1)]

        while stack:
            word, level = stack.pop(0)

            for i in range(len(word)):
                for char in "abcdefghijklmnopqrstuvwxyz":
                    new_word = word[:i] + char + word[i + 1:]

                    if new_word == endWord:
                        return level + 1
                    elif new_word in valid_word:
                        stack.append((new_word, level + 1))
                        valid_word.remove(new_word)

        return 0

这里我们可以打印一下转换的流程图,hot 有多层 level 出发,第二条路径走到了 cog,即结束遍历,当然 log 也可以走到 cog 只不过已经不需要了。

hot 2 -> lot 3

hot 2 -> dot 3 -> dog 4 -> cog 5

hot 2 -> dot 3 -> log 4 

◆ 双向 BFS 

class Solution(object):
    def ladderLength(self, beginWord, endWord, wordList):
        """
        :type beginWord: str
        :type endWord: str
        :type wordList: List[str]
        :rtype: int
        """
        # 去重使用
        valid_word = set(wordList)

        # 边界条件
        if endWord not in wordList or len(wordList) == 0:
            return 0

        # 双向 BFS
        begin, end, step = {beginWord}, {endWord}, 1


        # 同时有元素才能继续,如果一遍没元素代表已中断,无法联通,直接结束
        while begin and end:

            # 减少排查的可能性,从单词少的方向排查,避免无效查询
            if len(begin) > len(end):
                begin, end = end, begin

            # 存储下一层
            next_level = set()
            # 遍历下一层的多个结果
            for word in begin:
                # 遍历每个位置
                for i in range(len(word)):
                    # a-z
                    for char in "abcdefghijklmnopqrstuvwxyz":
                        # 节省无必要的替换
                        if char != word[i]:
                            new_word = word[:i] + char + word[i + 1:]
                            # 二者相遇即返回
                            if new_word in end:
                                return step + 1
                            if new_word in valid_word:
                                next_level.add(new_word)
                                valid_word.remove(new_word)

            # 指针替换
            begin = next_level
            step += 1

        return 0

已经将详细的注释加在代码里了,从 {start},{end} 两个方向查找,每次只找短的缩小无效查询的次数,这其实也是一种剪枝的策略,正所谓图中有真意欲辨已忘言:

◆ 双向 BFS + 剪枝

class Solution(object):
    def ladderLength(self, beginWord, endWord, wordList):
        """
        :type beginWord: str
        :type endWord: str
        :type wordList: List[str]
        :rtype: int
        """
        # 去重使用
        valid_word = set(wordList)

        if endWord not in wordList or len(wordList) == 0:
            return 0

        # 剪枝优化
        s = set()
        for word in wordList:
            for char in word:
                s.add(char)

        s = ''.join(list(s))

        # 双向 BFS
        begin, end, step = {beginWord}, {endWord}, 1

        while begin and end:

            if len(begin) > len(end):
                begin, end = end, begin

            # 存储下一层
            next_level = set()
            for word in begin:
                for i in range(len(word)):
                    # a-z
                    for char in s:
                        # 节省无必要的替换
                        if char != word[i]:
                            new_word = word[:i] + char + word[i + 1:]

                            if new_word in end:
                                return step + 1
                            if new_word in valid_word:
                                next_level.add(new_word)
                                valid_word.remove(new_word)

            # 指针替换
            begin = next_level
            step += 1

        return 0

上面的两个方法在构建 new_word 时都遍历了所有 26 个字母 char,其实我们可以根据 end_word 的去重字符进行状态空间压缩,从而减少无意义的遍历,因为 char not in end_word 则 new_word 必定 not in end_word,从而优化时间复杂度。 

2.Min-Gen-Mutation [433]

最小基因突变: https://leetcode.cn/problems/minimum-genetic-mutation/description/ 

◆ BFS

class Solution(object):
    def minMutation(self, startGene, endGene, bank):
        """
        :type startGene: str
        :type endGene: str
        :type bank: List[str]
        :rtype: int
        """
        if not bank:
            return -1

        bank = set(bank)
        if endGene not in bank:
            return -1

        stack = [(startGene, 0)]

        while stack:
            gene, level = stack.pop(0)

            for i in range(len(gene)):
                for char in "ACGT":
                    new_gene = gene[:i] + char + gene[i + 1:]

                    if new_gene == endGene:
                        return level + 1

                    if new_gene in bank:
                        stack.append((new_gene, level + 1))
                        bank.remove(new_gene)

        return -1

和上一题异曲同工之妙,只不过从单词接龙变成基因 🧬 接龙,每次修改的地方有限。

◆ 双向 BFS

class Solution(object):
    def minMutation(self, startGene, endGene, bank):
        """
        :type startGene: str
        :type endGene: str
        :type bank: List[str]
        :rtype: int
        """
        if not bank:
            return -1

        bank = set(bank)
        if endGene not in bank:
            return -1

        # 初始化首尾
        front, back, step = {startGene}, {endGene}, 0

        while front and back:

            next_front = set()

            # 遍历当前层 Gene
            for gene in front:
                print(gene)
                for i in range(len(gene)):
                    for char in "ACGT":
                        new_gene = gene[:i] + char + gene[i + 1:]
                        # 相遇了
                        if new_gene in back:
                            return step + 1
                        # 下一层突变
                        if new_gene in bank:
                            next_front.add(new_gene)
                            bank.remove(new_gene)

            # 取短的遍历加速
            if len(next_front) > len(back):
                front, back = back, next_front
            else:
                front = next_front

            step += 1

        return -1

和上面异曲同工,老曲新唱,相当于再温习一遍。其加速点就是左右替换,优先遍历可能性少的情况。

四.总结

这节内容 '双向 BFS' 起始也包含着很多剪枝的策略,所以其也属于优化搜索方式的方法之一,下一节我们介绍高级搜索的最后一块内容: A* 启发式搜索。

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

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

相关文章

1.1数算选择题(循环队列、二叉树、查找、堆、顺序表、生成树、哈夫曼树、排序)

循环队列 front:头指针 rear:尾指针 m:循环队列的长度 元素个数(rear-frontm)%m 19-114048%408 11-194032%4032 二叉树 入度出度,n-1n0n1n2-1n12n2,有n21n0,对于完全二叉树,度为1的节点要…

挑战Transformer的新架构Mamba解析以及Pytorch复现

今天我们来详细研究这篇论文“Mamba:具有选择性状态空间的线性时间序列建模” Mamba一直在人工智能界掀起波澜,被吹捧为Transformer的潜在竞争对手。到底是什么让Mamba在拥挤的序列建中脱颖而出? 在介绍之前先简要回顾一下现有的模型 Transformer:以其注意力机制而…

重学JavaScript高级(八):ES6-ES12新增特性学习

ES6-ES12新增特性学习 ES6–对象字面量增强 属性的简写方法的简写计算属性名 let name "zhangcheng" //我想让sum作为obj的key值 let objKey "sum" let obj {//属性名的简写name//等同于name:name//方法的简写running(){}//等同于running:function()…

RT-DETR 更换骨干网络之 MobileNetV3 | 《搜寻 MobileNetV3》

论文地址:https://arxiv.org/abs/1905.02244 代码地址:https://github.com/xiaolai-sqlai/mobilenetv3 我们展示了基于互补搜索技术和新颖架构设计相结合的下一代 MobileNets。MobileNetV3通过结合硬件感知网络架构搜索(NAS)和 NetAdapt算法对移动设计如何协同工作,利用互…

使用 Open3D 的 3D LiDAR 可视化:用于自动驾驶的 2D KITTI 深度框架-含数据集+源码

3D LiDAR传感器(或)3维 光探测和测距是一种先进的发光仪器,能够像我们人类一样在3维空间中感知现实世界。这项技术尤其彻底改变了地球观测、环境监测、侦察以及现在的自动驾驶领域。它提供准确和详细数据的能力有助于增进我们对环境和自然资源的理解和管理。 在这篇权威研究…

仿stackoverflow名片与b站名片实现(HTML、CSS)

目录 前言一、仿stackoverflow名片HTMLCSS 二、仿b站名片HTMLCSS 素材 前言 学习自ACwing - Web应用课 一、仿stackoverflow名片 HTML <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport&…

【hyperledger-fabric】使用couchDB

简介 本文章主要参考来自于官方文档使用CouchDB以及 https://www.bilibili.com/video/BV1Li4y1f7ex/?spm_id_frompageDriver&vd_source2c5f2831e1c63d3a20045b167ae044e6 B站视频&#xff0c;还是非常感谢up主提供了学习的思路。 为什么要使用couchDB&#xff1f; 原文…

如何将Redis、Zookeeper、Nacos配置为Windows系统的一个服务

说明&#xff1a;当我们在Windows上开发时&#xff0c;不可避免的会用到一些中间件&#xff0c;如Redis、Zookeeper、Nacos等等&#xff0c;当在项目中使用到本地的这些服务器时&#xff0c;我们需要把本地的服务器启动&#xff0c;会开启下面这样的一个窗口。 Redis服务器&am…

vscode使用npm安装element-UI并添加router路由

npm安装vue&#xff0c;添加淘宝镜像-CSDN博客 elementUI安装与配置 安装可以看我上一篇文章 vscode控制台输入指令 npm i element-ui -S 安装完成后在目录结构打开下图文件 可以看到多了一行elementui就代表安装成功了 下面是项目常用的结构 安装完成后需要启用elementU…

稀疏矩阵的三元组表示----(算法详解)

目录 基本算法包括&#xff1a;&#xff08;解释都在代码里&#xff09; 1.创建 2.对三元组元素赋值 3.将三元组元素赋值给变量 4.输出三元组 5.转置&#xff08;附加的有兴趣可以看看&#xff09; 稀疏矩阵的概念&#xff1a;矩阵的非零元素相较零元素非常小时&#xff…

自承载 Self-Host ASP.NET Web API 1 (C#)

本教程介绍如何在控制台应用程序中托管 Web API。 ASP.NET Web API不需要 IIS。 可以在自己的主机进程中自托管 Web API。 创建控制台应用程序项目 启动 Visual Studio&#xff0c;然后从“开始”页中选择“新建项目”。 或者&#xff0c;从“ 文件 ”菜单中选择“ 新建 ”&a…

了解VR虚拟现实的沉浸式效果及其技术特点!

VR虚拟现实体验装置作为近年来人气火爆的科技产品&#xff0c;以其独特的沉浸式体验效果吸引了众多用户&#xff0c;那么&#xff0c;你知道这种VR体验装置是如何实现沉浸式体验效果的吗&#xff1f;它又具备了哪些技术特点呢&#xff1f; 一、真实的场景体验 VR虚拟现实技术通…

html的全选反选

一、实验题目 html实现选择框的全选和反选 二、实验代码 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>全选和反选</title></head><body><ul>兴趣爱好</ul><input id"all"…

深入理解 Hadoop (四)HDFS源码剖析

HDFS 集群启动脚本 start-dfs.sh 分析 启动 HDFS 集群总共会涉及到的角色会有 namenode, datanode, zkfc, journalnode, secondaryName 共五种角色。 JournalNode 核心工作和启动流程源码剖析 // 启动 JournalNode 的核心业务方法 public void start() throws IOException …

数据湖存储解决方案之Iceberg

1.Iceberg是什么&#xff1f; Apache Iceberg 是由 Netflix 开发开源的&#xff0c;其于2018年11月16日进入 Apache 孵化器&#xff0c;是 Netflix 公司数据仓库基础。Apache Iceberg设计初衷是为了解决Hive离线数仓计算慢的问题&#xff0c;经过多年迭代已经发展成为构建数据…

<蓝桥杯软件赛>零基础备赛20周--第13周--DFS剪枝

报名明年4月蓝桥杯软件赛的同学们&#xff0c;如果你是大一零基础&#xff0c;目前懵懂中&#xff0c;不知该怎么办&#xff0c;可以看看本博客系列&#xff1a;备赛20周合集 20周的完整安排请点击&#xff1a;20周计划 每周发1个博客&#xff0c;共20周。 在QQ群上答疑&#x…

在生产环境中使用uWSGI来运行Flask应用

安装uwsgi pip install uwsgi -i https://pypi.tuna.tsinghua.edu.cn/simple安装不上则使用以下命令&#xff1a; conda install -c conda-forge uwsgi 当您成功安装uwsgi后&#xff0c;您可以通过以下步骤来测试uwsgi是否安装成功&#xff1a; 创建一个Python脚本&#xff…

WPS或word中英文字母自动调整大小写,取消自动首字母大写,全部英文单词首字母大小写变换方法

提示&#xff1a;写英文论文时&#xff0c;如何实现英文字母大小写的自动切换&#xff0c;不用再傻傻的一个字母一个字母的编辑了&#xff0c;一篇文章搞定WPS与Word中字母大小写切换 文章目录 一、WPS英文单词大小写自动修改与首字母大写调整英文字母全部由大写变成小写 或 小…

C++ Web框架Drogon初体验笔记

这段时间研究了一下C的Web框架Drogon。从设计原理上面来说和Python的Web框架是大同小异的&#xff0c;但是难点在于编译项目上面&#xff0c;所以现在记录一下编译的过程。下面图是我项目的目录。其中include放的是头文件&#xff0c;src放的是视图文件&#xff0c;static放的是…

e2studio开发磁力计LIS2MDL(2)----电子罗盘

e2studio开发磁力计LIS2MDL.2--电子罗盘 概述使用硬件视频教学样品申请源码下载环境磁场建模消除硬铁误差软铁干扰演示主程序 概述 本文将介绍如何使用 LIS2MDL 传感器来读取数据来转化为指南针。 地磁场强度范围约为 23,000 至 66,000 nT &#xff0c;并且可以建模为磁偶极子…