0-1 bfs 是一种可以在 O(n+m) 时间求解只含有 0,1 两种边权的单源最短路的算法。这种情形下效率比 dijkstra 更高,和 dijkstra 一样好写(甚至更好写一点)。
我们知道边权仅为 1 的最短路(可以看做无权图最短路)可以通过纯 bfs 使用 O(n+m) 解决,而 dijkstra 是 O((m+n)logn) 的,我从这里引入。
dijkstra 之所以有个 logn 就是因为取距离最小的点时存在瓶颈,那么为什么 bfs 只需要取队头呢?因为有了边权仅为 1 的特性,因此无论什么时候,队头到队尾,对应的距离都是单调不降的。
为了方便叙述,我们将 bfs 中队列里的每个元素(代表节点编号)u 都替换为 du 后的序列记作 q,q 的开头对应原队列开头。
比如 bfs 中队列 (2,4,3),那么我们的 q 会表示为 (d2,d4,d3)。因图而异,这个 q 可能是 (0,1,1),也有可能是 (2,3,3)……
在无权图最短路中我们每次只会把 q 的开头 D pop 掉,然后把若干个(可能没有)D+1 push 到队列的尾部,那么显然当 q 初始为 (0) 时,这样操作一定可以恒定使得 q 单调不降,并且事实上,任何时刻上 q 一定可以表示成 D,D,⋯,D,D+1,D+1,⋯,D+1 的形式,也就是先一段 D 后一段 D+1 的形式(可以全都是 D 或 D+1)。不信你试试看。
但是在 0-1 bfs 中,我们把 D pop 掉之后,push 进去的点有两种情况:D 和 D+1。如果我们直接 push 到尾部会破坏单调性。会出现类似于 D,D,D+1,D,D+1 这种支离破碎的情况……怎么办呢?
0-1 bfs 的精髓就来了:
- 把队头 u 弹出,遍历 u 连向的所有节点 v(D=du);
- 尝试松弛 v,如果成功:
- 如果 u→v 边权为 1,那么把 v push 到队尾(把 D+1 放到 q 的尾部);
- 如果 u→v 边权为 0,那么把 v push 到队头(把 D 放在 q 的头部)。
这样处理后 q 仍然可以时刻表示成 D,D,⋯,D,D+1,D+1,⋯,D+1 的形式。原因也是显然的。
另外,为了保证 dijkstra 每个点只会入队和出队一次,我们需要 vis 数组和松弛操作并用;
而无权最短路 bfs 可以把 vis 数组和松弛操作随便丢一个。
这个想想就知道,无权 bfs 是一层一层往下推,节点 u 第一次被搜索到的深度就是它的最短路,因此后面再访问 u 不再需要进行任何操作。
但是 0-1 bfs,我们举个例子,有一张图由以下两部分构成:
- 1→2→3→4→5→6,权值均为 0;
- 1→6,权值为 1。
我们会先从 1 拓展到 2 和 6,d6 会先被写上 1(假的),后面才能从 5 搜索到 6 从而使 d6=0,这个修改 d6 的操作需要松弛。所以松弛不能丢。
但是 0-1 bfs 直接丢掉 vis 数组是可以的。我们假设当前节点为 u,正在遍历出边 (u,v,w∈{0,1}):
- 当 w=0 时显然 dv=du 就是 v 确定的最短路了;
- 当 w=1 时,dv 会被标记成 du+1,那么 dv 实际的最短路可能是 du+1 也有可能是 du(显然只有这两种情况),因此 v 后面可能又会被重新松弛为 du 一次,而且也就最多这一次了。
所以一个点最多被入队两次,时间复杂度可以接受。所以不需要 vis 数组~
由于 0-1 bfs 中需要给队列的两端 push,需要使用到双端队列,因此也叫双端队列 bfs 和 dqbfs(?)。
代码就不上了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2020-10-02 放球问题 学习笔记