一句话:从起点出发,一圈一圈往外走,每圈多走一步。第一次碰到终点时,走的就是最短的路。
这个方法叫BFS(广度优先搜索),是走迷宫最常用、最好懂的方法。
一、生活里的例子:往地上倒一杯水
在起点倒一杯水:
- 第 1 秒,水流到起点旁边的格子
- 第 2 秒,再往外流一格
- 墙挡住的地方,水过不去
水最先流到终点的那条路,就是最短路。BFS 就是在模仿这个过程。
二、准备一个小迷宫
S . # . . . . # . # # . . . . . # # # . . . . # ES起点,E终点.能走,#是墙- 每次只能上下左右走一格
三、手动"倒水":看每个格子要走几步
从 S 开始,把"走到这里要几步"写进格子:
0 1 # 7 8 1 2 # 6 # # 3 4 5 6 - # # # 7 - - - # 80是起点,旁边的格子是1,再旁边是2……-表示水流不到(被墙围住了)- 终点 E 是 8 → 最短要走 8 步
怎么找出路线?从终点倒着走:8 → 7 → 6 → … → 0,每次走到比自己小 1 的格子。
四、程序需要三样东西
| 东西 | 作用 | 比喻 |
|---|---|---|
| 队列 Queue | 存放"接下来要去看的格子" | 排队,先来的先处理 |
| visited 数组 | 记录哪些格子来过了 | 在地上做记号,不走回头路 |
| from 数组 | 记录"我是从哪个格子走过来的" | 留面包屑,最后用来找回路线 |
五、完整代码(可以直接运行)
新建一个 C# 控制台项目,把下面的代码全部粘进去:
usingSystem;usingSystem.Collections.Generic;classProgram{staticvoidMain(){string[]maze={"S.#..","..#.#","#....",".###.","...#E",};List<(intr,intc)>path=FindPath(maze);if(path==null){Console.WriteLine("走不通!");return;}Console.WriteLine($"最短要走{path.Count-1}步:");// 把路线用 * 画出来char[][]map=newchar[maze.Length][];for(inti=0;i<maze.Length;i++)map[i]=maze[i].ToCharArray();foreach(var(r,c)inpath)if(map[r][c]=='.')map[r][c]='*';foreach(varrowinmap)Console.WriteLine(newstring(row));}staticList<(intr,intc)>FindPath(string[]maze){introws=maze.Length;intcols=maze[0].Length;// 1. 找到起点和终点(intr,intc)start=(0,0),end=(0,0);for(intr=0;r<rows;r++)for(intc=0;c<cols;c++){if(maze[r][c]=='S')start=(r,c);if(maze[r][c]=='E')end=(r,c);}// 上、下、左、右 四个方向int[]dr={-1,1,0,0};int[]dc={0,0,-1,1};varqueue=newQueue<(intr,intc)>();varvisited=newbool[rows,cols];varfrom=new(intr,intc)[rows,cols];// 2. 起点先入队,做记号queue.Enqueue(start);visited[start.r,start.c]=true;// 3. 不断从队列里拿格子出来,往四周扩散while(queue.Count>0){varcur=queue.Dequeue();if(cur==end)returnBuildPath(from,start,end);// 到终点了for(inti=0;i<4;i++){intnr=cur.r+dr[i];intnc=cur.c+dc[i];if(nr<0||nr>=rows||nc<0||nc>=cols)continue;// 出界if(maze[nr][nc]=='#')continue;// 是墙if(visited[nr,nc])continue;// 来过了visited[nr,nc]=true;// 做记号from[nr,nc]=cur;// 记住从哪来queue.Enqueue((nr,nc));// 排队,等会儿再从它往外走}}returnnull;// 队列空了还没到终点 → 走不通}// 4. 从终点沿着 from 倒着走回起点staticList<(intr,intc)>BuildPath((intr,intc)[,]from,(intr,intc)start,(intr,intc)end){varpath=newList<(intr,intc)>();varcur=end;while(cur!=start){path.Add(cur);cur=from[cur.r,cur.c];}path.Add(start);path.Reverse();// 倒过来,变成 起点 → 终点returnpath;}}运行结果
最短要走 8 步: S.#.. **#.# #**** .###* ...#E*就是找到的路线。
六、代码其实就四步
- 找起点终点
- 起点入队,做记号
- 循环:拿出一个格子 → 看上下左右 → 能走、没来过的就做记号、记来路、入队
- 到终点后,沿着
from倒着走回去,得到路线
七、其他走迷宫的方法
| 方法 | 怎么走 | 特点 |
|---|---|---|
| BFS(本篇) | 一圈一圈往外漫 | ✅ 一定找到最短路,好懂 |
| DFS | 一条路走到黑,撞墙再回头 | 能找到路,但不一定最短 |
| A* | BFS + “优先朝终点方向走” | 更快,游戏里的寻路基本都用它 |
A* 就是 BFS 的升级版。先把 BFS 弄懂,A* 只需要多加一点点东西。
八、常见的坑
| 坑 | 现象 | 原因 |
|---|---|---|
| 程序卡死 / 内存爆掉 | 一直跑不停 | 忘了visited,在两个格子之间来回走 |
| 报错数组越界 | IndexOutOfRangeException | 忘了检查出界 |
| 路线方向反了 | 从终点画到起点 | 忘了path.Reverse() |
| 和 Unity 坐标对不上 | 角色往反方向走 | 数组的行 r 是往下数的,而 Unity 的y 是往上的(上一篇讲的坐标系问题) |
一句话总结
BFS 走迷宫 = 从起点像水一样一圈圈往外漫,做记号不走回头路,记住每格是从哪来的。第一次到终点就是最短路,再顺着记录倒着走回去,就得到路线。