双向宽度优先搜索 (Bidirectional BFS) 算法适用于如下的场景:

  1. 无向图
  2. 所有边的长度都为 1 或者长度都一样
  3. 同时给出了起点和终点

以上 3 个条件都满足的时候,可以使用双向宽度优先搜索来求出起点和终点的最短距离。

算法描述

双向宽度优先搜索本质上还是BFS,只不过变成了起点向终点和终点向起点同时进行扩展,直至两个方向上出现同一个子节点,搜索结束。我们还是可以利用队列来实现:一个队列保存从起点开始搜索的状态,另一个保存从终点开始的状态,两边如果相交了,那么搜索结束。起点到终点的最短距离即为起点到相交节点的距离与终点到相交节点的距离之和。

Q.双向BFS是否真的能提高效率?
假设单向BFS需要搜索 N 层才能到达终点,每层的判断量为 X,那么总的运算量为 X ^ NXN. 如果换成是双向BFS,前后各自需要搜索 N / 2 层,总运算量为 2 * X ^ {N / 2}2∗XN/2。如果 N 比较大且X 不为 1,则运算量相较于单向BFS可以大大减少,差不多可以减少到原来规模的根号的量级。

from collections import deque

def doubleBFS(start, end):
if start == end:
return 1 # 分别从起点和终点开始的两个BFS队列
startQueue, endQueue = deque(), deque()
startQueue.append(start)
endQueue.append(end)
step = 0 # 从起点开始和从终点开始分别访问过的节点集合
startVisited, endVisited = set(), set()
startVisited.add(start)
endVisited.add(end)
while len(startQueue) and len(endQueue):
startSize, endSize = len(startQueue), len(endQueue)
# 按层遍历
step += 1
for _ in range(startSize):
cur = startQueue.popleft()
for neighbor in cur.neighbors:
if neighbor in startVisited: # 重复节点
continue
elif neighbor in endVisited: # 相交
return step
else:
startVisited.add(neighbor)
startQueue.append(neighbor)
step += 1
for _ in range(endSize):
cur = endQueue.popleft()
for neighbor in cur.neighbors:
if neighbor in endVisited:
continue
elif neighbor in startVisited:
return step
else:
endVisited.add(neighbor)
endQueue.append(neighbor) return -1

  

学习建议

Bidirectional BFS 掌握起来并不是很难,算法实现上稍微复杂了一点(代码量相对单向 BFS 翻倍),掌握这个算法一方面加深对普通 BFS 的熟练程度,另外一方面,基本上写一次就能记住,如果在面试中被问到了如何优化 BFS 的问题,Bidirectional BFS 几乎就是标准答案了。

参考资料

https://www.geeksforgeeks.org/bidirectional-search

应用例题

Shortest Path in Undirected Graph
Knight Shortest Path
Knight Shortest Path II

BFS算法的优化 双向宽度优先搜索的更多相关文章

  1. 【算法入门】广度/宽度优先搜索(BFS)

    广度/宽度优先搜索(BFS) [算法入门] 1.前言 广度优先搜索(也称宽度优先搜索,缩写BFS,以下采用广度来描述)是连通图的一种遍历策略.因为它的思想是从一个顶点V0开始,辐射状地优先遍历其周围较 ...

  2. 算法基础⑦搜索与图论--BFS(宽度优先搜索)

    宽度优先搜索(BFS) #include<cstdio> #include<cstring> #include<iostream> #include<algo ...

  3. 层层递进——宽度优先搜索(BFS)

    问题引入 我们接着上次“解救小哈”的问题继续探索,不过这次是用宽度优先搜索(BFS). 注:问题来源可以点击这里 http://www.cnblogs.com/OctoptusLian/p/74296 ...

  4. 【BFS宽度优先搜索】

    一.求所有顶点到s顶点的最小步数   //BFS宽度优先搜索 #include<iostream> using namespace std; #include<queue> # ...

  5. 搜索与图论②--宽度优先搜索(BFS)

    宽度优先搜索 例题一(献给阿尔吉侬的花束) 阿尔吉侬是一只聪明又慵懒的小白鼠,它最擅长的就是走各种各样的迷宫. 今天它要挑战一个非常大的迷宫,研究员们为了鼓励阿尔吉侬尽快到达终点,就在终点放了一块阿尔 ...

  6. 挑战程序2.1.5 穷竭搜索>>宽度优先搜索

    先对比一下DFS和BFS         深度优先搜索DFS                                   宽度优先搜索BFS 明显可以看出搜索顺序不同. DFS是搜索单条路径到 ...

  7. [宽度优先搜索] FZU-2150 Fire Game

    Fat brother and Maze are playing a kind of special (hentai) game on an N*M board (N rows, M columns) ...

  8. 宽度优先搜索--------迷宫的最短路径问题(dfs)

    宽度优先搜索运用了队列(queue)在unility头文件中 源代码 #include<iostream>#include<cstdio>#include<queue&g ...

  9. "《算法导论》之‘图’":深度优先搜索、宽度优先搜索(无向图、有向图)

    本文兼参考自<算法导论>及<算法>. 以前一直不能够理解深度优先搜索和广度优先搜索,总是很怕去碰它们,但经过阅读上边提到的两本书,豁然开朗,马上就能理解得更进一步. 下文将会用 ...

随机推荐

  1. bzoj2839 集合计数 组合计数 容斥原理|题解

    集合计数 题目描述 一个有N个元素的集合有2^N个不同子集(包含空集),现在要在这2^N个集合中取出若干集合(至少一个),使得它们的交集的元素个数为K,求取法的方案数,答案模1000000007.(是 ...

  2. ReentrantLock源码简析

    概念 ReentrantLock,可重入锁.在多线程中,可以通过加锁保证线程安全. 加锁和解锁 加锁: public void lock() { sync.lock(); } 解锁 public vo ...

  3. 利用ApplicationListener和ContextRefreshedEvent加载自己的beanPool

    基本原理: 1.Spring的ApplicationListener和ContextRefreshedEvent一般都是成对出现的. 2.在IOC的容器的启动过程中,当所有的bean都已经处理完成之后 ...

  4. Java通过poi读取excel中文件

    maven依赖 <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</a ...

  5. golang gRPC(持续更新)

    如何开启 gRPC 日志 设置 GRPC_GO_LOG_SEVERITY_LEVEL 环境变量, 可选项:["info", "warning", "e ...

  6. 分享一个Linux C++消息通信框架TCPSHM

    由于本人从事行业关系,Linux环境下的低延迟通信是我关注的技术之一.要达到极端的低延迟,当然同机器内IPC比网络通信快,而Linux IPC方式中无疑是共享内存延迟最低.不过相对于TCP这种通用的通 ...

  7. cmdb知识总结

    cmdb面试 1.paramiko模块的作用与原理 2.cmdb是什么 3.为什么要开发CMDB? 4.你们公司有多少台服务器?物理机?虚拟机? 5.你的CMDB是如何实现的? 6.CMDB都用到了哪 ...

  8. 深入理解 Linux Cgroup 系列(一):基本概念

    原文链接:深入理解 Linux Cgroup 系列(一):基本概念 Cgroup 是 Linux kernel 的一项功能:它是在一个系统中运行的层级制进程组,你可对其进行资源分配(如 CPU 时间. ...

  9. ASP.NET Core中如何显示[PII is hidden]的隐藏信息

    有时候我们在ASP.NET Core项目运行时,发生在后台程序中的错误会将关键信息隐藏为[PII is hidden]这种占位符,如下所示: 而知道这些关键信息,有时候对我们调试程序是非常重要的.所以 ...

  10. .NET / C# 时间与时间戳的转换

    时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数. 我们在计算时间戳时应为1970年01月01日到指定时间. 应当注 ...