前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库BFS 无权图 最短路
算法算法图算法

为什么 BFS 能在无权图中求出单源最短路径?

BFS 按层扩展,每个节点第一次被访问时一定经过最少边数,因此无权图中首次到达即最短。

前端进阶之旅 · 一题精讲更新于 2026.09.05
算法#图算法
先看核心答案读代码示例
理解线索

层序扩展保证最短性

  1. 队列先进先出保证按层顺序处理节点
  2. 距离单调递增后入队节点距离不小于先入队
  3. 访问标记防重复避免重复入队和距离覆盖

仅适用于无权图(所有边权均视为 1);若带权(权值不全为 1),需使用 Dijkstra 等算法。

核心回答

先记住这个答案

BFS 从源点出发,逐层扩展邻接点。由于无权图中每条边长度相同,首次访问某个节点时经过的边数必然最少。队列保证按距离递增顺序处理节点,因此每个节点的距离在第一次被设置时就已确定。路径还原通过记录每个节点的前驱节点,从目标回溯到源点即可。

  • BFS 按距离递增访问节点
  • 首次访问即确定最短距离
  • 前驱数组还原最短路径

层序扩展为何保证最短

BFS 从源点开始,将其所有邻接点入队并记录距离为 1。处理完源点后,再从队列取出最早入队的节点,扩展它的未被访问的邻接点,距离设为当前节点距离加 1。由于队列先进先出,距离为 d 的节点必然在距离为 d+1 的节点之前被处理,形成逐层扩展。每个节点一旦被访问就立即标记,不再可能被更远路径更新。假设存在一条更短的路径,那么它的最后一条边必然来自某个已访问的下一层节点,这与按层扩展矛盾。因此首次访问时距离就是最短边数。

该机制可形式化为:访问节点 v 时,它的距离 d[v] 已是最短。若存在另一条路径到 v 的边数更少,则那条路径上必有一个节点 w 先被访问但尚未扩展,导致矛盾。实际上,BFS 访问节点的顺序就是按距离非递减排列的。这种性质使得 BFS 不需要像 Dijkstra 那样比较距离,只需一次访问即可确定结果,时间复杂度 O(V+E)。

社交网络最少转发次数

假设一个社交网络有 10 万个用户,用户间关注关系构成有向无权图,需要计算从某大 V 到另一用户的至少需要多少次转发才能触达。输入为邻接表,边数约 30 万。从大 V 节点执行 BFS,同时记录每个用户的前驱。当目标节点被首次访问时,立即得到最短转发次数,并通过前驱链回溯路径,如 A→B→C。整个过程在线性时间 O(V+E) 内完成,且没有 Dijkstra 优先队列的额外开销。

若改用朴素 DFS 找最短路径,可能先深入一条长链,需要遍历所有可能路径才能确认最短,最坏指数级。BFS 因为按层扩展,第一个到达目标节点的路径必然最短,无需搜索全部。此场景还要求记录具体转发路径,前驱数组正好满足,若只求距离则一个整数数组即可。

无权图 BFS 最短距离与前驱JavaScript
function bfsShortestPath(adj, s, t) {
  const n = adj.length;
  const dist = new Array(n).fill(-1);
  const prev = new Array(n).fill(-1);
  const q = [s];
  let head = 0;
  dist[s] = 0;
  while (head < q.length) {
    const v = q[head++];
    if (v === t) break;
    for (const u of adj[v]) {
      if (dist[u] === -1) {
        dist[u] = dist[v] + 1;
        prev[u] = v;
        q.push(u);
      }
    }
  }
  if (dist[t] === -1) return { dist: -1, path: [] };
  const path = [];
  for (let cur = t; cur !== -1; cur = prev[cur]) path.push(cur);
  return { dist: dist[t], path: path.reverse() };
}

// 测试:0-1-2-3, 0-4-3
const adj = [[1,4],[0,2],[1,3],[2,4],[0,3]];
console.log(JSON.stringify(bfsShortestPath(adj,0,3)));
// 孤立点
const adj2 = [[],[],[]];
console.log(JSON.stringify(bfsShortestPath(adj2,0,1)));
// 自环和重边
const adj3 = [[1,1],[0,0]];
console.log(JSON.stringify(bfsShortestPath(adj3,0,1)));
查看输出与解释
{"dist":2,"path":[0,4,3]}
{"dist":-1,"path":[]}
{"dist":1,"path":[0,1]}

JavaScript 环境可直接运行。第一个图 0 到 3 的最短路径是 0-4-3,长度 2;第二个图不连通,返回 -1;第三个图有自环和重边,BFS 仍能正确得到距离 1。此实现使用索引指针(head)避免数组 shift 造成的额外开销,确保队列出入队均为 O(1),总体复杂度 O(V+E)。

BFS 最短路的适用边界

BFS 只在所有边权重相等(通常为 1)时有效。若边权不同,层序扩展不再对应路径长度累加,首次访问未必最短。例如一条直接边权 5 先访问,后到达的间接路径边权和反而可能更小。此时必须使用 Dijkstra 或 0-1 BFS(若权值为 0 和 1)等考虑实际权重的算法。若目标不可达,BFS 会遍历完所有可达节点后结束;队列仍会处理所有可达节点。

对于无权图,BFS 时间复杂度为 O(V+E),空间 O(V)。若图特别大且需要多源点,可初始化多个源点入队,但需注意正确设置初始距离和访问标记。若存在负权边,BFS 完全失效,因为路径长度可能因负边而递减,不能用层序思想。此时应改用 Bellman-Ford 或 SPFA。

回答前,多想一步

容易答错的地方

认为 BFS 只能用于无向图
BFS 对无向图和有向图都适用,只要边权为 1。有向图中 BFS 从源点沿出边扩展同样得到最短路径,只是不可达范围不同。
误以为需要松弛操作
无权图中首次访问即最短,无需比较距离更新。可结合边权为 1 的性质,每个节点只入队一次,避免重复处理。
试着用自己的话回答

面试官还会怎么问?

BFS 如何还原具体路径?

维护一个 parent 数组,记录每个节点第一次被哪个节点扩展。从目标节点沿 parent 链回溯至源点,反转得到路径。若目标不可达则无路径。

BFS 能否处理有向带环图?

可以。访问标记可防止死循环,BFS 在带环图中依然能正确求最短距离,因为环不会使最短路径变短,只会让遍历重复访问,访问标记避免重复入队。

如果边权不是 1 但都是非负整数,BFS 可以改吗?

不能直接改。例如边权为 1 和 2 时,层数不等于距离,不能直接用 BFS。若边权仅为 0 和 1,才需使用 0-1 BFS;对于一般非负整权,应使用 Dijkstra 算法。若权值较小,也可以拆分为多个单位边,但图规模会增大。

从一道题,走向一组知识

把知识连起来

图算法

Dijkstra 用二叉堆和朴素数组实现的复杂度分别是多少,各自适合什么图?

同属「图算法」专题,接着看 Dijkstra 复杂度 堆优化 在具体场景中的处理方式。

数组与双指针

可变滑动窗口求最短满足条件的子数组时,收缩窗口的条件如何判断?

继续了解 可变滑动窗口 最短子数组,补充本题涉及的 算法 相关知识。

参考资料

  • Breadth-first search¶

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 层序扩展为何保证最短
  3. 社交网络最少转发次数
  4. BFS 最短路的适用边界
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑