在数据结构与算法学习中,深度优先搜索(DFS)和广度优先搜索(BFS)是两种最基础也最常用的图遍历算法。它们不仅是面试高频考点,更是解决路径规划、拓扑排序、连通性分析等问题的核心工具。本文将通过一个完整的 C++ 迷宫游戏实例,带大家深入理解 DFS 与 BFS 的工作原理、代码实现及适用场景,让抽象的算法变得直观可感。
观前提示:本文使用AI写作
一、迷宫游戏整体介绍
首先,我们先对这个 C++ 迷宫游戏做一个整体了解,明确其功能与核心模块,为后续分析 DFS 和 BFS 的应用做铺垫。
1.1 游戏核心功能
- 随机迷宫生成:通过算法自动生成有通路的迷宫(墙与路径分布随机)
- 玩家交互:支持方向键 / WASD 控制玩家移动,ESC 键退出游戏
- 路径验证:确保生成的迷宫存在从起点(S)到终点(E)的通路
- 最短路径计算:预先计算起点到终点的最短步数,用于最终得分评估
- 游戏结算:到达终点后显示玩家步数、最短步数及最终得分
1.2 核心数据结构
// 迷宫单元格状态(枚举)
enum Cell { WALL = 0, PATH = 1, START = 2, END = 3, PLAYER = 4 };
// 坐标结构(存储迷宫中任意点的位置)
struct Point { int x, y; Point(int x = 0, int y = 0) : x(x), y(y) {} };
// 迷宫类核心成员
class MazeGame {
private:
vector<vector<int>> maze; // 二维向量存储迷宫数据(WIDTH列×HEIGHT行)
Point start, end, player; // 起点、终点、玩家位置
int steps; // 玩家已走步数
int minSteps; // 最短路径步数
// ... 成员函数
};
1.3 游戏运行流程
- 初始化迷宫:调用generateMaze()生成随机迷宫
- 路径验证:调用hasPath()确保迷宫有解(起点到终点连通)
- 计算最短路径:调用calculateMinSteps()获取最短步数
- 进入主循环:run()函数处理按键输入、更新玩家位置、刷新界面
- 游戏结束:玩家到达终点后计算得分并显示结果
二、深度优先搜索(DFS):迷宫生成与路径验证
深度优先搜索(Depth-First Search,简称 DFS)的核心思想是 “一条路走到黑”:从起点出发,优先探索当前路径的最深节点,若遇到死胡同则回溯,直到找到目标或遍历所有节点。其典型实现依赖栈(Stack) 或递归,本文实例中使用栈实现。
2.1 DFS 的应用场景 1:随机迷宫生成
迷宫生成的核心需求是 “创建连通的路径网络,同时保留足够的墙”。这里使用DFS 迷宫生成算法(也叫 “递归回溯法”),步骤如下:
算法原理
- 初始化迷宫:所有单元格设为墙(WALL)
- 选择起点:通常选迷宫内部点(避免在边缘),设为路径(PATH)并压入栈
- 栈非空时循环:
- 弹出栈顶节点(当前探索点)
- 随机打乱 4 个方向(上、下、左、右),避免迷宫模式固定
- 对每个方向,计算 “两步外” 的节点(避免路径过密):
- 若两步外节点在迷宫内部且为墙:
- 将两步外节点设为路径
- 将当前节点与两步外节点之间的墙设为路径(打通通道)
- 重新将当前节点压入栈(后续可能继续探索其他方向)
- 将两步外节点压入栈(继续探索新路径)
- 跳出当前方向循环,进入新节点探索
核心代码实现
void generateMaze() {
// 1. 初始化全部为墙
maze = vector<vector<int>>(HEIGHT, vector<int>(WIDTH, WALL));
// 2. 设定起点(1,1)和终点(HEIGHT-2, WIDTH-2)(边缘留1层墙)
start = Point(1, 1);
end = Point(HEIGHT - 2, WIDTH - 2);
// 3. DFS栈初始化:起点压栈,设为路径
stack<Point> s;
s.push(start);
maze[start.x][start.y] = PATH;
while (!s.empty()) {
Point current = s.top();
s.pop();
// 4. 随机打乱方向(0-上,1-下,2-左,3-右)
vector<int> directions = {0, 1, 2, 3};
for (int i = 0; i < 4; ++i) {
swap(directions[i], directions[rand() % 4]);
}
// 5. 探索每个方向
for (int dir : directions) {
// 计算两步外的节点(避免路径相邻)
int nx = current.x + dx[dir] * 2;
int ny = current.y + dy[dir] * 2;
// 检查两步外节点是否合法(在内部且为墙)
if (nx > 0 && nx < HEIGHT - 1 && ny > 0 && ny < WIDTH - 1 && maze[nx][ny] == WALL) {
// 打通通道:两步外节点和中间节点设为路径
maze[nx][ny] = PATH;
maze[current.x + dx[dir]][current.y + dy[dir]] = PATH;
// 回溯准备:当前节点重新压栈,新节点压栈
s.push(current);
s.push(Point(nx, ny));
break; // 优先探索新路径,下次循环处理其他方向
}
}
}
// 标记起点和终点
maze[start.x][start.y] = START;
maze[end.x][end.y] = END;
player = start; // 玩家初始位置在起点
}
为什么用 DFS 生成迷宫?
DFS 生成的迷宫具有 “狭长、多分支” 的特点,路径更具探索性(类似迷宫游戏的体验)。相比其他算法(如 Prim 算法),DFS 实现简单且能保证迷宫连通性,非常适合小型迷宫场景。
2.2 DFS 的应用场景 2:迷宫路径验证
生成迷宫后,需要确保存在从起点到终点的通路(避免玩家无法到达终点)。这里使用 DFS 判断迷宫的连通性,核心是遍历所有可达节点,检查是否包含终点。
算法原理
- 初始化 visited 数组:标记节点是否已访问(避免重复遍历)
- 起点压栈,标记为已访问
- 栈非空时循环:
- 弹出栈顶节点
- 若为终点,返回 true(存在通路)
- 遍历 4 个方向,若相邻节点合法(在迷宫内部、未访问、非墙):
- 标记为已访问,压入栈
- 栈空时返回 false(无通路)
核心代码实现
bool hasPath() {
// 初始化访问标记数组
vector<vector<bool>> visited(HEIGHT, vector<bool>(WIDTH, false));
stack<Point> s;
s.push(start);
visited[start.x][start.y] = true;
while (!s.empty()) {
Point current = s.top();
s.pop();
// 找到终点,返回有通路
if (current.x == end.x && current.y == end.y) {
return true;
}
// 探索4个方向
for (int i = 0; i < 4; ++i) {
int nx = current.x + dx[i];
int ny = current.y + dy[i];
// 检查节点合法性:在范围内、未访问、非墙
if (nx >= 0 && nx < HEIGHT && ny >= 0 && ny < WIDTH &&
!visited[nx][ny] && maze[nx][ny] != WALL) {
visited[nx][ny] = true;
s.push(Point(nx, ny));
}
}
}
// 遍历完所有可达节点,未找到终点
return false;
}
2.3 DFS 的核心特点与适用场景
| 特点 | 说明 |
|---|---|
| 实现方式 | 栈(迭代)或递归(函数调用栈本质也是栈) |
| 遍历顺序 | 深度优先,优先探索当前路径的最深节点 |
| 空间复杂度 | O (V)(V 为节点数,栈最多存储所有节点) |
| 最优解 | 不保证找到最优解(可能陷入局部路径) |
| 适用场景 | 迷宫生成、连通性判断、拓扑排序、深度优先遍历(如二叉树前 / 中 / 后序) |
注意:递归实现 DFS 时需注意栈溢出问题(如迷宫过大时,递归深度过深会导致栈溢出),因此本文采用迭代(栈)实现更安全。
三、广度优先搜索(BFS):最短路径计算
广度优先搜索(Breadth-First Search,简称 BFS)的核心思想是 **“逐层扩散”:从起点出发,先遍历所有距离为 1 的节点,再遍历距离为 2 的节点,直到找到目标或遍历所有节点。其典型实现依赖队列(Queue),且能保证找到最短路径 **(在无权图中)。
3.1 BFS 的应用场景:迷宫最短路径计算
游戏需要预先计算起点到终点的最短步数,用于最终得分评估(玩家步数越接近最短步数,得分越高)。BFS 是解决 “无权图最短路径” 的最优选择。
算法原理
- 初始化 dist 数组:存储起点到每个节点的最短距离(-1 表示未访问)
- 起点入队,dist [起点] 设为 0(距离为 0)
- 队列非空时循环:
- 出队队首节点
- 若为终点,记录 dist 值并返回(即最短步数)
- 遍历 4 个方向,若相邻节点合法(在迷宫内部、未访问、非墙):
- dist [相邻节点] = dist [当前节点] + 1(距离 + 1)
- 相邻节点入队
- 队列空时表示无通路(本文中已通过 hasPath () 确保有通路,此情况不会发生)
核心代码实现
void calculateMinSteps() {
// 初始化距离数组:-1表示未访问
vector<vector<int>> dist(HEIGHT, vector<int>(WIDTH, -1));
queue<Point> q;
q.push(start);
dist[start.x][start.y] = 0; // 起点距离为0
while (!q.empty()) {
Point current = q.front();
q.pop();
// 找到终点,记录最短距离并返回
if (current.x == end.x && current.y == end.y) {
minSteps = dist[current.x][current.y];
return;
}
// 探索4个方向
for (int i = 0; i < 4; ++i) {
int nx = current.x + dx[i];
int ny = current.y + dy[i];
// 检查节点合法性:在范围内、未访问、非墙
if (nx >= 0 && nx < HEIGHT && ny >= 0 && ny < WIDTH &&
dist[nx][ny] == -1 && maze[nx][ny] != WALL) {
dist[nx][ny] = dist[current.x][current.y] + 1; // 距离+1
q.push(Point(nx, ny));
}
}
}
}
为什么 BFS 能找到最短路径?
在无权图中(迷宫的路径权重均为 1),BFS 的 “逐层扩散” 特性确保:第一次到达终点时,所经过的路径一定是最短的。例如,距离起点为 3 的节点一定在距离为 2 的节点之后被遍历,因此不会出现 “绕远路” 的情况。而 DFS 无法保证这一点,可能会先探索到一条较长的路径。
3.2 BFS 的核心特点与适用场景
| 特点 | 说明 |
|---|---|
| 实现方式 | 队列(FIFO,先进先出) |
| 遍历顺序 | 广度优先,逐层扩散,先访问近节点再访问远节点 |
| 空间复杂度 | O (V)(队列最多存储所有节点,在 “完全图” 场景下空间消耗可能比 DFS 大) |
| 最优解 | 保证找到无权图的最短路径 |
| 适用场景 | 最短路径计算(如迷宫、地图导航)、层次遍历(如二叉树层序遍历)、连通性判断 |
四、DFS 与 BFS 的核心区别对比
通过迷宫游戏的实例,我们可以清晰地看到 DFS 与 BFS 的差异。下表从多个维度对两者进行总结:
| 对比维度 | 深度优先搜索(DFS) | 广度优先搜索(BFS) |
|---|---|---|
| 数据结构 | 栈(Stack)或递归栈 | 队列(Queue) |
| 遍历顺序 | 深度优先,“一条路走到黑” | 广度优先,“逐层扩散” |
| 空间复杂度 | O (V)(最坏情况),递归可能栈溢出 | O (V)(最坏情况),无栈溢出问题 |
| 最短路径 | 不保证找到最优解 | 保证找到无权图的最短路径 |
| 迷宫生成效果 | 路径狭长、多分支,探索性强 | 路径均匀、分支少,不适合生成迷宫 |
| 适用场景 | 迷宫生成、拓扑排序、深度遍历 | 最短路径、层次遍历、邻居查找 |
| 访问标记 | 需 visited 数组避免重复遍历 | 需 visited 数组或 dist 数组避免重复遍历 |
五、游戏完整运行与体验
5.1 编译与运行
该代码基于 C++ 标准库编写,依赖<conio.h>(按键输入)和<windows.h>(清屏),适合在 Windows 环境下使用:
- 将代码保存为MazeGame.cpp
- 使用 MinGW 或 Visual Studio 编译:g++ MazeGame.cpp -o MazeGame.exe
- 运行MazeGame.exe,即可开始游戏
5.2 游戏操作
- 移动:方向键(上 / 下 / 左 / 右)或 WASD 键(W 上 / S 下 / A 左 / D 右)
- 退出:ESC 键
- 得分规则:步数越接近最短步数,得分越高(最短步数得 100 分,每多 10% 步数减 10 分,最低 10 分)
六、总结与扩展
通过这个迷宫游戏实例,我们不仅掌握了 DFS 和 BFS 的实现细节,更理解了它们的 “应用场景差异”——DFS 适合 “探索性” 任务(如迷宫生成),BFS 适合 “最优路径” 任务(如最短路径计算)。
6.1 算法扩展
- 加权图最短路径:若迷宫路径有权重(如某些路径需要消耗更多步数),BFS 不再适用,需使用 Dijkstra 算法或 A * 算法
- 迷宫优化:可增加 “地图缩放”“障碍物动态生成” 等功能,进一步锻炼图算法的应用能力
- 可视化增强:可使用 SFML 或 OpenGL 替换控制台输出,实现更精美的迷宫图形界面
6.2 学习建议
- 手动模拟 DFS 和 BFS 的遍历过程(如在纸上画一个小迷宫,模拟栈 / 队列的变化)
- 尝试用递归实现 DFS(注意栈溢出问题)
- 基于本文代码,添加 “显示最短路径” 功能(BFS 计算时记录路径,游戏中用特殊符号标记)
希望本文能帮助你从 “代码实现” 到 “原理理解” 再到 “场景应用”,全面掌握 DFS 和 BFS 这两个基础算法。算法学习的核心在于 “举一反三”,后续可尝试用这两种算法解决更多问题(如岛屿数量、最大路径和等),逐步提升算法思维能力。
3600

被折叠的 条评论
为什么被折叠?



