从你早上出门通勤,到导航APP给你推荐最短路径,再到游戏里角色找最近的道具,背后都藏着一种简单又好用的算法——广度优先搜索,也就是我们常说的BFS。很多人觉得算法离日常很远,其实BFS的逻辑就像你问邻居“去地铁站最近走哪”,第一层的邻居(跟你隔1步的人)直接给你答案,这些答案就是距离最短的,不用等更远处的人(隔2步的)回答,这就是BFS能解决最短路径问题的核心原因。
一、为什么BFS能搞定最短路径
简单来说,BFS是按“层”来遍历节点的,你可以把所有要找的路径看成水波,丢一颗石头(起点)到水里,最先碰到的岸边(目标节点)的波纹,就是最短距离的路径。因为每一层波纹的扩散,都是比上一层多走一步的距离,所以第一次遇到目标节点的时候,那一定是步数最少的路径,不会有比这更短的了,这就是BFS天生适合无权图最短路径的道理。
二、BFS解决最短路径的具体实践(带完整示例)
我们用一个大家都熟悉的迷宫场景来演示,迷宫用二维数组表示,0代表可以走的路,1代表墙,起点是左上角的[0,0],终点是右下角的[4,4],要找出从起点到终点的最短步数。这次的示例我们用JavaScript来实现,每一步都加了清晰的注释,你可以直接运行这段代码看结果。
2.1 场景设定
迷宫是5x5的,具体的布局是: [ [0,0,0,1,0], [0,1,0,0,0], [0,0,0,1,0], [1,1,0,0,0], [0,0,0,1,0] ] 起点是(0,0),终点是(4,4),我们要找从起点到终点的最少步数,每走一步算1次,上下左右四个方向都可以走,不能穿墙。
2.2 代码实现
// 技术栈:JavaScript
// 定义迷宫(0=可走,1=墙)
const maze = [
[0,0,0,1,0],
[0,1,0,0,0],
[0,0,0,1,0],
[1,1,0,0,0],
[0,0,0,1,0]
];
// 定义起点和终点坐标
const start = [0,0];
const end = [4,4];
// 定义四个可移动的方向:上、下、左、右
const directions = [[-1,0], [1,0], [0,-1], [0,1]];
// BFS函数,找最短路径步数
function bfsShortestPath(maze, start, end) {
// 如果起点就是终点,直接返回0步
if (start[0] === end[0] && start[1] === end[1]) return 0;
// 初始化队列,每个元素存当前位置和已走步数
const queue = [[...start, 0]];
// 初始化访问过的节点,避免循环走回头路,大小和迷宫一样
const visited = maze.map(row => row.map(() => false));
// 标记起点为已访问
visited[start[0]][start[1]] = true;
// 开始BFS循环,直到队列空(没有路径)或者找到终点
while (queue.length > 0) {
// 从队列头部取出第一个元素(先进先出,BFS的关键)
const [currentX, currentY, steps] = queue.shift();
// 遍历四个方向
for (const [dx, dy] of directions) {
const newX = currentX + dx;
const newY = currentY + dy;
// 检查新坐标是否在迷宫范围内,是否可走,且未被访问过
if (newX >=0 && newX < maze.length && newY >=0 && newY < maze[0].length
&& maze[newX][newY] === 0 && !visited[newX][newY]) {
// 检查是否到达终点,是的话返回当前步数+1
if (newX === end[0] && newY === end[1]) {
return steps + 1;
}
// 标记为已访问,加入队列,步数+1
visited[newX][newY] = true;
queue.push([newX, newY, steps + 1]);
}
}
}
// 队列空了还没找到,说明没有路径
return -1;
}
// 调用函数并打印结果
const result = bfsShortestPath(maze, start, end);
console.log("迷宫最短路径步数是:", result);
你运行这段代码,会得到结果是7,这就是这个迷宫里从起点到终点的最短步数,手动数也绝对不会更少。
三、BFS在最短路径的实际应用场景
除了刚才的迷宫,BFS的应用场景还有很多,都集中在无权图的场景里——也就是两个节点之间的“距离”都是等同的,不需要考虑权重差异: 第一个是导航APP的同区域路线规划,比如你在城市里,要从公司去附近的商场,周边道路的长度差异不大,APP找最近路线的基础逻辑就是BFS,哪怕实际产品会结合其他优化,BFS是核心底层; 第二个是游戏里的NPC寻路,比如角色扮演游戏里角色要找最近的任务NPC,地图是规整的网格,每个格子的移动成本一致,BFS能快速算出步数最少的路线; 第三个是社交网络的好友层级查询,对应“六度空间理论”——想知道你和某个陌生人之间隔了几个朋友,这种关系图是典型的无权图,用BFS可以直接算出最短的连接层级,也就是最少需要通过几个朋友联系到对方。
四、BFS的优缺点分析
任何算法都不是万能的,BFS也有明确的适用边界,得搞清楚什么时候用、什么时候换其他方案: 先说它的优点:
- 逻辑简单,代码易实现,不需要复杂的排序或优先级处理,按层遍历就能搞定;
- 找的是绝对最短路径,因为是按层推进,第一次碰到目标节点的步数一定是最少的,不会出现绕路的情况;
- 小规模场景下效率足够,比如几百×几百的网格迷宫,BFS的遍历速度完全能满足需求; 再来说它的缺点:
- 完全不适合有权图,也就是节点间的距离不一样的场景,比如有的道路长1公里、有的长3公里,这时候得用Dijkstra算法,BFS派不上用场;
- 空间开销大,需要存储所有访问过的节点和队列,要是碰到1000×1000级别的大图,会占用大量内存,甚至出现内存溢出;
- 大节点规模下效率不如其他算法,比如节点数过万的图,BFS的遍历速度会明显变慢,因为要一层一层完整扫描。
五、使用BFS的注意事项
要把BFS用对,避开常见的坑,得记住这几个关键点: 第一,必须标记访问过的节点,也就是刚才代码里的visited数组,如果漏掉这步,会在迷宫里来回循环走回头路,永远找不到终点; 第二,队列必须是先进先出,绝对不能用栈,用栈就变成了深度优先搜索(DFS),会走最深的路径,不是最短的,这是BFS和DFS的核心区别,千万别搞混; 第三,一定要处理边界,坐标不能小于0,也不能超过迷宫的行数和列数,不然会跑出数组,直接报错; 第四,终点判断的位置要一致,要么刚入队就判断,要么出队遍历方向时判断,不能乱改位置,不然会出现漏判或误判。
六、总结
BFS其实就是把生活里“一层一层问答案”的思路写成了代码,理解起来一点都不难,核心逻辑就是“按层遍历,首次碰到即最短”。它虽然只能解决无权图的最短路径,但胜在简单高效,应用场景覆盖了日常很多需求——从导航路线到游戏寻路,再到社交网络的好友层级查询,都是它的用武之地。哪怕遇到有权图或大规模场景,只需要把BFS稍作调整(比如双向BFS,同时从起点和终点同时遍历,能大幅提升速度),就能满足需求。对于所有想入门算法的开发者来说,BFS都是必须掌握的基础,它是理解更复杂算法的敲门砖。
Comments