BFS判重和双向广搜

news2025/1/8 5:28:03

目录

一、前言

二、BFS判重

1、set 判重

2、字典判重

3、跳蚱蜢(2017年省赛,lanqiaoOJ题号642)

(1)字典去重、用 list 实现队列

(2)set() 去重、用 list 实现队列

(3)set() 去重、用 deque 实现队列

三、双向广搜


一、前言

本文主要讲了BFS如何判重和双向广搜。

二、BFS判重

  • BFS = 队列
  • BFS:逐步扩展下一层,把扩展出的下一层状态放进队列中处理。
  • 如果这些状态有相同的,只需搜一次,只需要进入队列一次。
  • 必须判重。

Python 判重方法:set、字典

1、set 判重

  • set() 函数创建一个无序、不重复元素集
  • 关系测试,删除重复数据,计算交集、差集、并集、补集。
a=set()
a.add("678");print(a)
a.add("123");print(a)
a.add("678")    #第二个"678"加不进集合了
print(a)
b=sorted(a);print(b);print()
s=set("aabc");print(s);print()  #a,b,c的输出顺序不定的,输出 {'a','b','c'}
s=set(["aabc","bca"]);print(s);print()      #输出{"aabc","bca"}

b=set()
b.add(678);print(b)
b.add(123);print(b)
b.add(678);print(b);print()

a=set([1,1,2,3,5]);print(a)
b=set([1,2,3,4]);print(b)
print(a-b)  #差,在集合a中但不在集合b中的元素
print(b-a)  #差
print(a&b)  #交,同时在集合a和b中的共同元素
print(a|b)  #并,包括集合a和b中所有元素

2、字典判重

  • 字典:无序、可变、有索引的集合。
  • 字典:用花括号定义,有键和值。
a={5:"xy"}
a[1]="cb"
a[2]="kd"
a[2]="af"
print(a);print()

b=sorted(a.items());print(b)
b=sorted(a.items(),key=lambda x:x[0]);print(b)  #按键排序
b=sorted(a.items(),key=lambda x:x[1]);print(b)  #按值排序
print()

a={"food":"xy","price":555}
a[3]=899
print(a)

3、跳蚱蜢(2017年省赛,lanqiaoOJ题号642)

【题目描述】

有 9 只盘子,排成 1 个圆圈。其中 8 只盘子内装着 8 只蚱蜢,有一个是空盘。

把这些蚱蜢顺时针编号为1~8。

每只蚱蜢都可以跳到相邻的空盘中,也可以再用点力,越过一个相邻的蚱蜢跳到空盘中。请你计算一下,如果要使得蚱蜢们的队形改为按照逆时针排列,并且保持空盘的位置不变(也就是1-8换位,2-7换位,...),至少要经过多少次跳跃?

  • 从起始状态到终止状态,求最少跳跃次数
  • 最短路径问题
  • 用BFS
  • 直接让蚱蜢跳到空盘有点麻烦,因为有很多蚱蜢在跳。
  • 反过来看,让空盘跳,跳到蚱蜢的位置,简单多了,只有一个空盘在跳。

【画圆为线】

  • 题目是一个圆圈,不好处理,用一个建模技巧 “化圆为线”,把圆形转换为线形。
  • 把空盘看成 0,有 9 个数字 {0,1,2,3,4,5,6,7,8},一个圆圈上的 9 个数字,拉直成了一条线上的 9 个数字,这条线的首尾两个数字处理成相连的。
  • 八数码问题:有 9 个数字 {0,1,2,3,4,5,6,7,8),共有 9! = 362880 种排列,不算多。

【最短路径】

初始状态:“012345678”, 目标状态:“087654321”。 

从初始状态 "012345678" 跳一次,有 4 种情况:"102345678"、“210345678”、 “812345670”、 “712345608”。然后从这 4 种状态继续跳到下一种状态,一直跳到目标状态为止。

用 BFS 扩展每一层。每一层就是蚱蜢跳了一次,扩展到某一层时发现终点 “087654321”,这一层的深度就是蚱蜢跳跃的次数。

【去重】

  • 如果不去重?
  • 第 1 步到第 2 步,有 4 种跳法;第 2 步到第 3 步,有 4*4 种;...;第 20 步,有 4^20 = 1万亿种。

  • 判重:判断有没有重复跳,如果跳到一个曾经出现过的情况,就不用往下跳了。一共只有 9! =362880 种情况。
  • 代码的复杂度:在每一层,能扩展出最少 4 种、最多 362880 种情况,最后算出的答案是 20 层,那么最多算 20*362880=7,257,600 次。在代码中统计实际的计算次数,是 1451452 次。
  • 队列:最多有 9!=362880 种情况进入队列。

(1)字典去重、用 list 实现队列

速度慢:3s

def insertQueue(q:list,dir:int,news:tuple,vis):     #q是列表,vis是字典,dir是方向
    pos=news[1]         #0的位置
    status=news[0]      #字符串
    insertPos=(pos+dir+9)%9     #新的0的位置
    #将字符串转为列表比较好处理
    t=list(status)      #先转换成列表
    t[pos],t[insertPos]=t[insertPos],t[pos]
    addStatus="".join(t)    #再由列表转换为一个字符串,join完是返回一个字符串
    if addStatus not in vis:
        vis[addStatus]=1    #向字典添加,去重
        q.append((addStatus,insertPos,news[2]+1))

q=[("012345678",0,0)]   #列表,用于实现队列,比较慢
vis={"012345678":1}     #字典,用于去重
while q:
    news=q.pop(0)   #news是一个元组
    if news[0]=="087654321":     #到达了目标状态,输出最少步数
        print(news[2])
        break
    insertQueue(q,-2,news,vis)  #拓展下一层的4种情况
    insertQueue(q,-1,news,vis)
    insertQueue(q,1,news,vis)
    insertQueue(q,2,news,vis)

(2)set() 去重、用 list 实现队列

def insertQueue(q:list,dir:int,news:tuple,vis):     
    pos=news[1]         #0的位置
    status=news[0]      
    insertPos=(pos+dir+9)%9     #新的0的位置
    #将字符串转为列表比较好处理
    t=list(status)      
    t[pos],t[insertPos]=t[insertPos],t[pos]
    addStatus="".join(t)    
    if addStatus not in vis:
        vis.add(addStatus)      #判重 
        q.append((addStatus,insertPos,news[2]+1))

q=[("012345678",0,0)]   
vis=set()
vis.add("012345678")    #判重
while q:
    news=q.pop(0)  
    if news[0]=="087654321":     
        print(news[2])
        break
    insertQueue(q,-2,news,vis) 
    insertQueue(q,-1,news,vis)
    insertQueue(q,1,news,vis)
    insertQueue(q,2,news,vis)

(3)set() 去重、用 deque 实现队列

速度快:1.4s

from collections import *
def insertQueue(q:deque,dir:int,news:tuple,vis:set):     
    pos=news[1]         #0的位置
    status=news[0]      
    insertPos=(pos+dir+9)%9     #新的0的位置
    #将字符串转为列表比较好处理
    t=list(status)      
    t[pos],t[insertPos]=t[insertPos],t[pos]
    addStatus="".join(t)    
    if addStatus not in vis:
        vis.add(addStatus)      
        q.append((addStatus,insertPos,news[2]+1))   #deque的append

q=deque()
q.append(("012345678",0,0))   
vis=set()
vis.add("012345678")    #判重
while q:
    news=q.popleft()  
    if news[0]=="087654321":     
        print(news[2])
        break
    insertQueue(q,-2,news,vis) 
    insertQueue(q,-1,news,vis)
    insertQueue(q,1,news,vis)
    insertQueue(q,2,news,vis)

三、双向广搜

  • 应用场景:有确定的起点 s 和终点 t;把从起点到终点的单向搜索,变换为分别从起点出发和从终点出发的“相遇”问题。
  • 操作:从起点 s(正向搜索)和终点 t(逆向搜索)同时开始搜索,当两个搜索产生相同的一个子状态 v 时就结束,v 是相遇点。得到的 s-v-t 是一条最佳路径。
  • 队列:一般用两个队列分别处理正向 BFS 和逆向 BFS.

【双向广搜的复杂度】

当下一层扩展的状态很多时,双向广搜能大大优化,减少大量搜索

【重新思考上面的例题】

  • 队列 q1:正向搜索
  • 队列 q2:逆向搜索

from queue import *
cnt=0
meet=False
def extend(q,m1,m2):    # m1和m2是字典
    global cnt
    global meet
    s=q.get()
    for i in range(len(s)):
        if s[i]=='0':
            break
    for j in range(4):
        cnt+=1          #统计计算个数
        news=list(s)    #用list比较方便
        if j==0:
            news[(i-2+9)%9],news[i]=news[i],news[(i-2+9)%9]
        if j==1:
            news[(i-1+9)%9],news[i]=news[i],news[(i-1+9)%9]
        if j==2:
            news[(i+1+9)%9],news[i]=news[i],news[(i+1+9)%9]
        if j==3:
            news[(i+2+9)%9],news[i]=news[i],news[(i+2+9)%9]
        a="".join(news) #重新转换成字符串
        if a in m2:
            print(m1[s]+1+m2[a])
            print(cnt)  #打印计算次数
            meet=True
            return
        if a not in m1:
            q.put(a)
            m1[a]=m1[s]+1   #向字典中添加
    meet=False

q1=Queue()
q2=Queue()
q1.put("012345678")
q2.put("087654321")
mp1={'012345678':0}
mp2={'087654321':0}     #定义字典,用于判重
while not q1.empty() and not q2.empty():
    if q1.qsize()<=q2.qsize():
        extend(q1,mp1,mp2)
    else:
        extend(q2,mp2,mp1)
    if meet==True:
        break
  • 由于起点和终点的串不同,正向 BFS 和逆向 BFS 扩展的下一层数量也不同,也就是进入 2 个队列的串的数量不同,先处理较小的队列,可以加快搜索速度。
from queue import *
cnt=0
meet=False
def extend(q,m1,m2):    # m1和m2是字典
    global cnt
    global meet
    s=q.get()
    for i in range(len(s)):
        if s[i]=='0':
            break
    for j in range(4):
        cnt+=1          #统计计算个数
        news=list(s)    #用list比较方便
        if j==0:
            news[(i-2+9)%9],news[i]=news[i],news[(i-2+9)%9]
        if j==1:
            news[(i-1+9)%9],news[i]=news[i],news[(i-1+9)%9]
        if j==2:
            news[(i+1+9)%9],news[i]=news[i],news[(i+1+9)%9]
        if j==3:
            news[(i+2+9)%9],news[i]=news[i],news[(i+2+9)%9]
        a="".join(news) #重新转换成字符串
        if a in m2:
            print(m1[s]+1+m2[a])
            print(cnt)  #打印计算次数
            meet=True
            return
        if a not in m1:
            q.put(a)
            m1[a]=m1[s]+1   #向字典中添加
    meet=False

q1=Queue()
q2=Queue()
q1.put("012345678")
q2.put("087654321")
mp1={'012345678':0}
mp2={'087654321':0}     #定义字典,用于判重
while not q1.empty() and not q2.empty():
    if q1.qsize()<=q2.qsize():
        extend(q1,mp1,mp2)
    else:
        extend(q2,mp2,mp1)
    if meet==True:
        break

用 cnt 统计运行了多少次: 54568次。

前面用普通BFS计算: 1451452次

双向广搜的计算量只有 4%

  • 为什么能优化这么多?
  • 在普通 BFS 中,如果不判重,到第 20 层扩展了 4^20 种状态。在双向广搜中,假设在第 10 层相遇,正向搜索和逆向搜索在第 10 层扩展的状态数量都是4^10
  • 从4^20到4^10,得到了极大优化。

以上,BFS判重和双向广搜

祝好

 

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

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

相关文章

setDaemon python守护进程,队列通信子线程

使用setDaemon()和守护线程这方面知识有关&#xff0c; 比如在启动线程前设置thread.setDaemon(True)&#xff0c;就是设置该线程为守护线程&#xff0c;表示该线程是不重要的,进程退出时不需要等待这个线程执行完成。这样做的意义在于&#xff1a;避免子线程无限死循环&#x…

IK分词工具

https://code.google.com/archive/p/ik-analyzer/ IK Analyzer是一个开源的&#xff0c;基于java语言开发的轻量级的中文分词工具包。从2006年12月推出1.0版开始&#xff0c; IKAnalyzer已经推出了4个大版本。最初&#xff0c;它是以开源项目Luence为应用主体的&#xff0c;结合…

ruoyi-vue版本(八)登陆页面的验证码是咋实现的

目录1 需求2 配置类3 逻辑1 需求 我们打开若依项目的登陆页面&#xff0c;看到有一个验证码功能&#xff0c;点击一下这个验证码&#xff0c;还会进行变换验证码&#xff0c;那么这个逻辑是咋实现的&#xff1b; 我们刚进这个页面&#xff0c;其实就调用了一个接口&#xff1…

整理指针相关练习

这里收录的是相关指针的练习&#xff0c;主要针对的是指针与sizeof之间的练习&#xff0c;练完你对指针的理解将更进一层喔一维数组指针练习字符数组指针练习二维数组指针练习练习总结&#xff1a;指针笔试真题一维数组指针练习 一维数组相关练习&#xff0c;下面答案是多少呢…

负载均衡的在线OJ

文章目录1.项目宏观结构(1)三个模块(2)项目宏观结构(3)编写顺序2.compile_server(1)compiler.hpp(2)runner.hpp(3)compile_run.hpp(4)compile_server.cc(5)Makefile(6)temp(7)编译运行模块总结3.comm(1)util.hpp(2)log.hpp(3)httplib.h4.基于MVC结构的OJ服务设计(oj_server)(1)…

java访问控制符/导入2023019

访问控制符&#xff08;定义的时候不加访问控制符&#xff0c;默认的就是default&#xff09;&#xff1a; 1.private&#xff08;当前类访问权限&#xff09;&#xff1a;如果类里的一个成员&#xff08;包括成员变量、方法和构造器等&#xff09;使用private访问控制符来修饰…

Java——数组中第k个最大的元素

题目链接 leetcode在线oj题——数组中第k个最大的元素 题目描述 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 k 个最大的元素。 请注意&#xff0c;你需要找的是数组排序后的第 k 个最大的元素&#xff0c;而不是第 k 个不同的元素。 你必须设计并实现时间复杂…

Move_base_flex(mbf)框架理解

本文章重点在 第二部分类图解析&#xff0c;第四部分代码解析 文章目录1. move_base_flex主体代码结构树2. move_base_flex 类图解析2.1 ROS2 navigation整体架构2.2 mbf类图主体思路详解2.2.1. 抽象层&#xff08;abstract层&#xff09;2.2.2. 外部信号输入&#xff08;Actio…

【NI Multisim 14.0虚拟仪器设计——放置虚拟仪器仪表(4通道示波器)】

目录 序言 &#x1f34d;放置虚拟仪器仪表 &#x1f349;4通道示波器 1.“时基”选项组 2.“通道”选项组 序言 NI Multisim最突出的特点之一就是用户界面友好。它可以使电路设计者方便、快捷地使用虚拟元器件和仪器、仪表进行电路设计和仿真。 首先启动NI Multisim 14.0…

C语言 通讯录最终版(动态内存+实时保存)

欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e;&#x1f49e; 前言&#xff1a; 上期通讯录我们实现了动态内存的申请&#xff0c;但数据依然是存放在内存中&#xff0c;当程序退出的时候&#xff0c;通讯录中的数据自然就不存在了&#xff0c;等下次运行通讯录程序的时候…

什么是REST和RESTful

REST&#xff08;Representational State Transfer&#xff09;表象化状态转变&#xff08;表述性状态转变&#xff09;&#xff0c;在2000年被提出&#xff0c;基于HTTP、URI、XML、JSON等标准和协议&#xff0c;支持轻量级、跨平台、跨语言的架构设计。是Web服务的一种新的架…

单身福利专场——Python采集某相亲地数据

嗨害大家好鸭&#xff01;我是小熊猫~ 咳咳年前最后一天… 一点单身福利… 我想… 应该会有需要的吧… 环境开发: Python 3.8Pycharm 模块使用: import parselimport requestsimport csvimport re 爬虫基本思路流程: 一. 数据来源分析: 1. 明确需求: 采集数据是什么 —…

Linux基本功系列之ping命令实战

文章目录一. 命令介绍二. 语法格式及常用选项三. 参考案例3.1 测试本机与指定网站服务器之间的网络连通性3.2 指定ping的次数3.3 指定时间间隔和次数3.4 设置TTL为2553.5 极快速的测试使用大包ping四. 使用ping命令常见问题总结前言&#x1f680;&#x1f680;&#x1f680; 想…

Java项目部署到云服务器的思路

Java项目部署到云服务器的思路 1 部署项目的前提条件 1.1 购买云服务器 我购买的是腾讯云的服务器,第一年享优惠88一年 cpu好像两核的,作为入门级的也算够用了 如果第二年该续费的时候,我记得因为收到备案什么因素的影响,要提前三个月就续费了,第二年续费价格应该是510 对于学…

思科与华为设备中的OSFP配置命令以及部分实例(超详细~~!!)

目录 一、OSPF相关配置命令 1.思科设备配置命令 &#xff08;1&#xff09;启动OSPF路由进程 &#xff08;2&#xff09;激活参与OSPF路由协议的接口&#xff0c;并且通告结构属于哪个区域的OSPF &#xff08;3&#xff09;配置路由器ID &#xff08;4&#xff09;配置被动…

苹果再次舍弃3纳米,对ASML是沉重打击,ASML得靠中国救命了

苹果在昨晚发布了新款M2 Pro和M2 Max芯片&#xff0c;这两款芯片都没有采用台积电的3纳米工艺&#xff0c;其实不仅是对台积电的打击&#xff0c;也是对ASML的打击&#xff0c;意味着ASML更先进的第二代EUV光刻机可能面临着没有太大需求的问题。一、ASML的愿望ASML当前的主要利…

day43|● 1049. 最后一块石头的重量 II ● 494. 目标和 ● 474.一和零

1049. 最后一块石头的重量 II 1.代码 class Solution { public:int lastStoneWeightII(vector<int>& stones) {int sum 0;for(int i: stones) {sum i;}int t sum;sum sum /2;vector<int>f(sum 1);for (int i 0; i < stones.size(); i) {for (int j …

怎样防止数据怎么泄露了

近年来&#xff0c;各种数据泄露事件越演越烈&#xff0c;数据泄密日益成为企业管理者的梦魇。数据泄密不仅给企业带来严重的直接经济损失&#xff0c;而且还在品牌价值、投资人关系、社会公众形象等多方面造成损害。因此&#xff0c;要想提升企业数据的安全性&#xff0c;就要…

容器虚拟化技术Docker(二)mysql主从配置案例、redis集群搭建及扩容、缩容案例详解

容器虚拟化技术Docker&#xff08;二&#xff09;mysql主从配置案例、redis集群搭建及扩容缩容案例详解 对docker不熟悉的可以参考&#xff1a; 容器虚拟化技术Docker&#xff08;一&#xff09;简介、安装、常见命令、数据卷、安装常规软件 1、Docker安装mysql主从复制 &am…

2023年微软发布的第一个补丁都有什么?

微软于 10 日发布了 2023 年的第一个更新&#xff0c;修复了其Windows操作系统和其他软件中的近 100 个安全漏洞。 2023 年第一个补丁星期二的亮点包括&#xff1a;Windows 中的零日漏洞、美国国家安全局报告的打印机软件缺陷&#xff0c;以及允许未经身份验证的远程攻击者建立…