当前位置: 首页 > news >正文

算法第十二期——BFS-双向广搜

双向广搜

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

双向广搜的复杂度

当下一层扩展的状态很多时,双向广搜能大大优化,减少大量搜索。
 图(1)单个BFS搜索到第四层需要搜索1+2+4+8+16=32次,二图(2)双向BFS只需要1+2+4+2+1=10次,显然效率更高。

注意: 双向广搜对二叉树有很大的优化,但对网格图并没有很好的效果。

例题:跳蚱蜢

 跳蚱蜢2017年第八届省赛,lanqiao0J题号642

【题目描述】

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

如下图所示: 有 9 只盘子,排成 1 个圆圈。 其中 8 只盘子内装着 8 只蚱蜢,有一个是空盘。 我们把这些蚱蜢顺时针编号为 1 ~ 8。

每只蚱蜢都可以跳到相邻的空盘中, 也可以再用点力,越过一个相邻的蚱蜢跳到空盘中。

请你计算一下,如果要使得蚱蜢们的队形改为按照逆时针排列, 并且保持空盘的位置不变(也就是 1−8 换位,2−7换位,...),至少要经过多少次跳跃?

【思路】 

队列q1:正向搜索

队列q2:逆向搜索
由于起点和终点的串不同,正向BFS和逆向BFS扩展的下一层数量也不同,也就是进入2个队列的串的数量不同,先处理较小的队列,可以加快搜索速度。

 建模

  • 直接让蚱蜢跳到空盘有点麻烦,因为有很多在跳蚱蜢。
  • 每一次跳跃有一只蚱蜢跳到空盘,如果我们反过来看,让空盘跳,跳到蚱蜢的位置,简单多了,每个队列只有一个空盘在跳。

化圆为线

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

最短路径

  •  用BFS扩展每一层,每一层就是蚱蜢(建模成‘’空盘‘’)跳了一次。
  • 初始状态(队列1):“012345678”,目标状态(队列2):“087654321”。
  • 从初始状态“012345678”跳一次,有4种情况:0跳到1(向右跳一个),2(向右跳两个),7(向左跳一个),8(向左跳两个)这几个点,即“1023456783"、“210345678”、“812345670”、“712345608",等价于交换了空盘编号0和空盘跳到的蚂蚱编号。同样的,从目标状态“087654321”跳一次也是4这种情况。
  • 然后从这4种状态继续跳到下一种状态,一直跳到目标状态为止,队列1的状态第一次出现在队列2中就是出现了最短路径,记录步数:正向搜索步数+当前这一步+逆向搜索步数。

【代码】 

from queue import *
cnt = 0
meet = False
def extend(q,m1,m2):
    global cnt
    global meet
    s = q.get() # 弹出队首状态
    for i in range (len(s) ):   # 找出该状态下0的位置
        if s[i]=='0': break
    for j in range(4): # 四种跳的情况:左1,左2,右1,右2
        cnt += 1            #统计计算次数
        news = list(s)      # 把字符串转成list,后面方便处理
        # 化圆为方
        if j==0: news[(i-2+9)%9] ,news[i] = news[i], news[(i-2+9)%9] # 往左跳1个,把跳到的数与0交换
        if j==1: news[(i-1+9)%9], news[i] = news[i], news[(i-1+9)%9] # 往左跳2个
        if j==2: news[(i+1+9)%9], news[i] = news[i], news[(i+1+9)%9] # 往右跳1个
        if j==3: news[(i+2+9)%9], news[i] = news[i], news[(i+2+9)%9] # 往右跳2个
        a = "".join(news)   # 处理完再转回字符串重新转成字符串
        if a in m2:         # 结束条件:第一次相遇(队列1的状态在队列2里出现过),就是最短路径
            print(m1[s] + 1 + m2[a])    # 正向搜索的步数+当前这一步+逆向搜索的步数
            # print(cnt)    # 打印计算次数:54560
            meet = True
            return
        if a not in m1:     # 若该状态不在队列1
            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) # 若队列1较小,先处理队列1
    else:                         extend(q2, mp2, mp1) # 若队列2较小,先处理队列2
    if meet==True: break    # 相遇就结束

 计算量:

  • 用cnt统计运行了多少次:54568次。
  • 前面用普通BFS计算:1451452次
  • 双向广搜的计算量只有4%

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

相关文章:

  • 深圳有做网站公司/成免费crm软件有哪些优点
  • 找人网站/免费注册
  • 武汉便宜做网站/青岛seo推广专员
  • 县政府门户网站建设方案/百度广告联盟
  • 行业网站建设公司/网络推广营销培训机构
  • 上海大型网站建设/日照网络推广
  • 基础数学(二)两数之和 三数之和
  • SpringCloud复习之Sleuth+Zipkin链路追踪实战
  • (02)Cartographer源码无死角解析-(51) 2D点云扫描匹配→ceres扫描匹配:CeresScanMatcher2D→平移旋转残差
  • Java 元注解
  • Java基础(二)
  • 第二章.线性回归以及非线性回归—特征缩放,交叉验证法,过拟合
  • SpreadJS.Release.16.0.2 Crack by Xacker
  • Spring、SpringMVC、SpringBoot、SpringCloud 框架常用注解说明
  • 【教学赛】金融数据分析赛题1:银行客户认购产品预测(0.9676)
  • Java图形化界面---JOptionPane
  • ubuntu20.04下出现protoc与gazebo版本问题
  • 通信电子、嵌入式类面试题刷题计划03