用队列解决迷宫问题(基于C#)

2 阅读3分钟

问题描述

我们会用二维数组表示迷宫,其中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# 的“高级工具”,它们是代码简洁的关键:

  1. int[,] maze (二维数组)

    • 在 C 语言中,你可能写 mg[8][8]
    • 在 C# 中,[,] 表示这是一个真正的二维矩形数组。访问时写 maze[x, y]
  2. (int X, int Y) (元组 Tuple)

    • 这是 C# 特有的轻量级结构。它不需要你像 C 语言那样定义 struct Point,可以直接把两个整数打包在一起,通过 p.Xp.Y 访问。
  3. var 关键字

    • 这是“隐式类型”。编译器会根据右边的值自动推断它是 Queue 还是 List
  4. Queue<T> (泛型队列)

    • 这是 C# 标准库提供的。不用手动管理 frontrear 指针了。
    • Enqueue():自动把元素加到末尾。
    • Dequeue():自动从头部取走元素

思路步骤

第一步:初始化

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;
}