问题描述
我们会用二维数组表示迷宫,其中0代表可以通过的方格,1代表墙体。如下所示:
int[,] maze = {
{ 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 1 },
{ 1, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 0 },
{ 0, 1, 0, 0, 0 }
};
我们使用队列解决的化简就是广度优先,会把所有路径都走完,然后找一条最短的。 这里我们使用C#来实现,因为C#内定许多方法,实现起来相对简单,下面会介绍一下用的函数和语法
C#语法
在看逻辑之前,先理解这几个 C# 的“高级工具”,它们是代码简洁的关键:
-
int[,] maze(二维数组) :- 在 C 语言中,你可能写
mg[8][8]。 - 在 C# 中,
[,]表示这是一个真正的二维矩形数组。访问时写maze[x, y]。
- 在 C 语言中,你可能写
-
(int X, int Y)(元组 Tuple) :- 这是 C# 特有的轻量级结构。它不需要你像 C 语言那样定义
struct Point,可以直接把两个整数打包在一起,通过p.X和p.Y访问。
- 这是 C# 特有的轻量级结构。它不需要你像 C 语言那样定义
-
var关键字:- 这是“隐式类型”。编译器会根据右边的值自动推断它是
Queue还是List。
- 这是“隐式类型”。编译器会根据右边的值自动推断它是
-
Queue<T>(泛型队列) :- 这是 C# 标准库提供的。不用手动管理
front和rear指针了。 Enqueue():自动把元素加到末尾。Dequeue():自动从头部取走元素
- 这是 C# 标准库提供的。不用手动管理
思路步骤
第一步:初始化
var queue = new Queue<(int X, int Y)>(); // 任务清单
var visited = new bool[rows, cols]; // 已经踩过的格子
var parent = new Dictionary<(int, int), (int, int)>(); // 记录前一个格子
visited的作用是防死循环。parent是最关键的。在 C 语言代码里,用Qu[rear].pre = front记录下标。在 C# 里,我们直接记录“坐标 A 是从 坐标 B 走过来的”。
第二步:循环搜索
while (queue.Count > 0) // 只要清单里还有没探索的点
{
var curr = queue.Dequeue(); // 取出排在最前面的点
if (curr == end) { ... } // 到终点了吗?到了就收工
for (int i = 0; i < 4; i++) // 尝试上下左右
{
int nx = curr.X + dx[i], ny = curr.Y + dy[i];
if (判定合法) // 没出界、不是墙、没去过
{
visited[nx, ny] = true;
parent[(nx, ny)] = curr; // 记录:(nx,ny) 是从 curr 走过来的
queue.Enqueue((nx, ny)); // 加入待探索名单
}
}
}
逻辑重点:
- BFS 保证了距离起点近的点先出队,距离远的点后出队。
- 每当我们发现一个新格子,我们都要通过
parent字典给它贴个标签,记下它的“介绍人”。
第三步:路径回溯
当找到终点后,我们手里只有一个 parent 字典。
for (var p = end; p != start; p = parent[p])
res.Add(p);
最终代码
using System;
using System.Collections.Generic;
// 1. 核心数据:0是路,1是墙
int[,] maze = {
{ 0, 0, 1, 0, 0 },
{ 0, 0, 1, 0, 1 },
{ 1, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 0 },
{ 0, 1, 0, 0, 0 }
};
var path = SolveBFS((0, 0), (4, 4), maze);
if (path != null)
path.ForEach(p => Console.Write($"({p.X},{p.Y}) "));
else
Console.WriteLine("无路可走");
// 2. 核心算法:BFS
List<(int X, int Y)> SolveBFS((int X, int Y) start, (int X, int Y) end, int[,] map)
{
int rows = map.GetLength(0), cols = map.GetLength(1);
var queue = new Queue<(int X, int Y)>();
var visited = new bool[rows, cols];
var parent = new Dictionary<(int, int), (int, int)>(); // 记录路径:当前点 -> 上一个点
queue.Enqueue(start);
visited[start.X, start.Y] = true;
int[] dx = { 0, 1, 0, -1 }, dy = { 1, 0, -1, 0 };
while (queue.Count > 0)
{
var curr = queue.Dequeue();
if (curr == end) // 找到终点,开始回溯路径
{
var res = new List<(int, int)>();
for (var p = end; p != start; p = parent[p]) res.Add(p);
res.Add(start);
res.Reverse();
return res;
}
for (int i = 0; i < 4; i++) // 检查四个方向
{
int nx = curr.X + dx[i], ny = curr.Y + dy[i];
if (nx >= 0 && nx < rows && ny >= 0 && ny < cols && map[nx, ny] == 0 && !visited[nx, ny])
{
visited[nx, ny] = true;
parent[(nx, ny)] = curr; // 记录是从哪个点走过来的
queue.Enqueue((nx, ny));
}
}
}
return null;
}