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

算法系列教程:1. BFS求无向无权图最短路径

示例图

3cd9e414-6dfa-4011-a030-5d6c5a685d39

BFS示例代码

function bfsShortestPath(graph, start, end) {const queue = [[start]];const visited = new Set([start]);while (queue.length > 0) {const path = queue.shift();const node = path[path.length - 1];if (node === end) return path;for (const neighbor of graph[node]) {if (!visited.has(neighbor)) {visited.add(neighbor);queue.push([...path, neighbor]);}}}return null;
}const graph = {A: ['C', 'B', 'D', 'E'],B: ['A', 'C', 'F', 'D'],C: ['A', 'B', 'F'],D: ['A', 'B'],E: ['A'],F: ['B', 'C']
};console.log(bfsShortestPath(graph, 'C', 'D'));

代码逐行解析

下面逐行解析这段代码在求解 C到D的最短路径 时的执行逻辑,重点跟踪队列、已访问集合的变化和路径探索过程:

1. 函数定义

function bfsShortestPath(graph, start, end) {
  • 定义函数bfsShortestPath,用于计算从start(此处为'C')到end(此处为'D')的最短路径。
  • 参数:graph(邻接表表示的无向无权图)、start(起始节点)、end(目标节点)。

2. 初始化队列和已访问集合

  const queue = [[start]];const visited = new Set([start]);
  • queue(队列):存储待探索的路径,初始时只有一条路径[start](即['C'],从C出发的初始路径)。
  • visited(集合):记录已访问的节点,避免重复探索(防止循环),初始时仅包含start(即'C')。

3. BFS核心循环(队列非空时持续探索)

  while (queue.length > 0) {
  • 只要队列中还有待探索的路径,就继续循环(BFS的核心:按“距离递增”顺序探索路径,确保第一个到达目标的路径是最短的)。

4. 取出队首路径并获取当前节点

    const path = queue.shift();const node = path[path.length - 1];
  • queue.shift():取出队列的第一个路径(队列“先进先出”特性,保证先探索距离C更近的路径)。
  • node:当前路径的最后一个节点(即“当前正在处理的节点”,后续需探索它的邻居)。

5. 判断是否到达目标节点

    if (node === end) return path;
  • 如果当前节点是目标节点end(即'D'),直接返回当前路径(BFS特性:此时的路径是最短路径)。

6. 遍历当前节点的所有邻居

    for (const neighbor of graph[node]) {
  • 遍历graph中当前节点node的所有邻居(即与node直接相连的节点)。

7. 处理未访问的邻居

      if (!visited.has(neighbor)) {visited.add(neighbor);queue.push([...path, neighbor]);}
  • 若邻居未被访问(!visited.has(neighbor)):

    • 标记为已访问(visited.add(neighbor)),避免重复处理。
    • 生成新路径(当前路径 + 该邻居),加入队列等待下一轮探索(queue.push([...path, neighbor]))。

8. 无路径时返回null

  }return null;
}
  • 若队列空了仍未找到D,说明C到D无路径,返回null(此处不触发,因为存在路径)。

9. 图的定义(邻接表)

const graph = {A: ['C', 'B', 'D', 'E'],B: ['A', 'C', 'F', 'D'],C: ['A', 'B', 'F'],D: ['A', 'B'],E: ['A'],F: ['B', 'C']
};
  • 定义无向无权图:每个节点的数组值是其直接相邻的节点。例如:

    • C的邻居是A、B、F(C与这三个节点直接相连)。
    • A的邻居包含D(A与D直接相连)。
    • B的邻居包含D(B与D直接相连)。

10. 调用函数并打印结果

console.log(bfsShortestPath(graph, 'C', 'D')); // 输出:['C', 'A', 'D']
  • 调用函数求C到D的最短路径,下面详细分析执行过程

关键执行步骤(C→D的探索过程)

初始状态:

  • 队列:[ ['C'] ](只有从C出发的初始路径)。
  • 已访问:{ 'C' }

第1次循环(处理路径['C']):

  • 取出路径['C'],当前节点node = 'C'

  • 判断:C !== D(继续)。

  • 遍历C的邻居:['A', 'B', 'F'](按数组顺序处理)。

    • 邻居A:未访问 → 标记visited = { 'C', 'A' },新路径['C', 'A']入队 → 队列:[ ['C', 'A'] ]
    • 邻居B:未访问 → 标记visited = { 'C', 'A', 'B' },新路径['C', 'B']入队 → 队列:[ ['C', 'A'], ['C', 'B'] ]
    • 邻居F:未访问 → 标记visited = { 'C', 'A', 'B', 'F' },新路径['C', 'F']入队 → 队列:[ ['C', 'A'], ['C', 'B'], ['C', 'F'] ]

第2次循环(处理路径['C', 'A']):

  • 取出路径['C', 'A'],当前节点node = 'A'

  • 判断:A !== D(继续)。

  • 遍历A的邻居:['C', 'B', 'D', 'E']

    • 邻居C:已访问(跳过)。
    • 邻居B:已访问(跳过)。
    • 邻居D:未访问 → 标记visited = { 'C', 'A', 'B', 'F', 'D' },新路径['C', 'A', 'D']入队 → 队列:[ ['C', 'B'], ['C', 'F'], ['C', 'A', 'D'] ]
    • 邻居E:未访问 → 标记visited = { 'C', 'A', 'B', 'F', 'D', 'E' },新路径['C', 'A', 'E']入队 → 队列:[ ['C', 'B'], ['C', 'F'], ['C', 'A', 'D'], ['C', 'A', 'E'] ]

第3次循环(处理路径['C', 'B']):

  • 取出路径['C', 'B'],当前节点node = 'B'

  • 判断:B !== D(继续)。

  • 遍历B的邻居:['A', 'C', 'F', 'D']

    • 邻居A、C、F:均已访问(跳过)。
    • 邻居D:已访问(第2次循环中已标记)→ 跳过(无新路径入队)。
  • 队列变为:[ ['C', 'F'], ['C', 'A', 'D'], ['C', 'A', 'E'] ]

第4次循环(处理路径['C', 'F']):

  • 取出路径['C', 'F'],当前节点node = 'F'
  • 判断:F !== D(继续)。
  • 遍历F的邻居:['B', 'C'] → 均已访问(无新路径入队)。
  • 队列变为:[ ['C', 'A', 'D'], ['C', 'A', 'E'] ]

第5次循环(处理路径['C', 'A', 'D']):

  • 取出路径['C', 'A', 'D'],当前节点node = 'D'
  • 判断:D === D → 满足条件,返回该路径。

最终结果

函数返回['C', 'A', 'D'],即C到D的最短路径是“C→A→D”(长度为2,经过2条边)。

补充说明

C到D其实还有另一条等长路径“C→B→D”(同样长度为2),但由于BFS中邻居的遍历顺序(C的邻居按['A', 'B', 'F']顺序处理),['C', 'A']路径先入队,因此其延伸出的['C', 'A', 'D']先被探索到,成为函数返回的结果。两条路径都是最短路径,BFS会返回“先被发现”的那条。

http://www.zskr.cn/news/45661.html

相关文章:

  • 2025年靠谱的装修品牌权威推荐
  • word导出图表 - IT
  • 2025年国内装修工程队排名:徐州领先企业一站式服务解析
  • 2025年平床身数控车床生产厂家口碑排行榜
  • 带着弟弟卖红薯
  • 2025年11月国内窗帘电机公司推荐排行榜
  • 2025年高科技数控机床供货商推荐
  • PR视频剪辑音频处理教程 School Of Motion – Premiere for Motion Designers
  • 行业内农业遮阳网渠道
  • 2025年智能中高考加盟电话推荐选哪家
  • 有了 AI 编程工具 Cursor,前端开发 “消失”,又回归全栈开发模式
  • 2025年光伏锡渣还原粉定制厂家推荐
  • 2025年权威的青少年组织领导力成长训练单位口碑排行
  • 2025年轧辊数控车床品牌推荐排行榜
  • 2025年智能控制与计算科学国际学术会议(ICICCS 2025)
  • 基于MATLAB的光纤光传播特性仿真
  • ModelScope 模型一键上线?FunModel 帮你 5 分钟从零到生产
  • 记录WPF 在清单列表设置了UIACESS为true,没有签名的报错“从服务器返回了一个参照”
  • 新手在哪里找预防感冒类公众号排版?
  • 2025年北京中央空调更换铜管维修护理权威推荐榜单:中央空调维修保养/中央空调电控系统改造升级/地源热泵进水维修护理精选
  • springboot框架非常简单清晰
  • 打破工业现场的“物理围墙”,如何让工程师在家也能改程序?
  • 近红外与可见光图像融合的多种方法实现
  • 赛博扫盲(2)
  • 基于PKHV3000系列无源高压衰减棒的应用案例
  • 2025年尼龙共挤膜直销厂家权威推荐:五层共挤膜/洁净尼龙多层共挤膜/无菌设备保护套厂家精选
  • 【RK3568 NPU实战】别再闲置你的NPU!手把手带你用迅为资料跑通Android AI检测Demo,附完整流程与效果
  • 智能守护绿水青山:视频融合平台EasyCVR在森林防火监控中的实战应用
  • 微算法科技(NASDAQ MLGO)将租赁权益证明(LPoS)共识算法与零知识证明结合,实现租赁代币的隐私保护
  • 在 Java 中实现插件化:使用 PF4J 的实战指南