Qt迷宫及路径获取:递归回溯生成、A*寻路与QGraphicsView实践 简介基于Qt框架的随机迷宫生成与最短路径获取项目源码适合正在学习图形界面开发、并查集数据结构以及启发式搜索算法的开发者阅读。项目演示了从二维数组迷宫建模、深度优先搜索随机生成连通迷宫到借助并查集快速维护格子连通关系再以A星算法计算起点到终点最短路径的完整流程同时包含可运行的Qt工程、界面布局及交互资源。压缩包共66个文件约28.14MB以动态链接库、源文件、头文件、图片资源和样式表为主另有工程解决方案与界面设计文件便于直接编译运行和后续修改。目前已有三百五十三人学习下载适合作为课程设计、算法可视化或迷宫小游戏的开发参考能够帮助读者把抽象的数据结构与图形界面结合起来加深对路径规划和集合算法的理解。1. 这是一套能改、能玩、能交差的 Qt 迷宫程序框架如果你在找“Qt随机迷宫及路径获取”多半是想做一个这样的桌面程序每次点击“生成”按钮界面上出现一张结构完全不同的迷宫再点“寻路”一条高亮路径从起点画到终点。这套程序对新手来说是把数据结构、图搜索算法和 Qt 的事件循环串起来的最好载体对熟手来说是一个可以直接扩展成地图编辑器、寻路性能测试台的骨架。它不涉及复杂的视觉模型也不依赖外部库纯 C 加 Qt Widgets 就能跑通非常适合拿来做课程设计、小工具原型或者只是想把经典迷宫算法在桌面端“捏”出来看看效果。我一开始做这个标题下的程序时想得过于简单以为难点全在算法。真正落地后才发现算法只占三分之一剩下三分之二是 Qt 的渲染架构选择、坐标对齐、线程与动画协作这些桌面端特有的细节。这篇文章我会按自己实践的顺序展开先讲生成算法选型和最小实现再讲路径搜索的选型与动画然后专门对比两套 Qt 绘图方案最后给你一份我踩过的坑清单和一个编辑器扩展方向。你可以直接照着代码敲也可以只读思路然后自己实现两种方式都能绕开我在初期翻过的车。2. 迷宫生成算法选型为什么我首推递归回溯而不是 Prim迷宫生成看起来简单实际上不同的算法生成的迷宫风格差异很大。如果你不去选型随便从网上抄一个乱七八糟的生成逻辑后面寻路、渲染、扩展示例都会跟着别扭。所以这里先把常见算法的区别和适用场景讲清楚再落到具体的 C 实现上。2.1 三种生成算法的本质区别与适用场景生成迷宫本质上是把一个网格里的“墙”和“路”做二值化处理。常见做法有三种递归回溯DFS 随机化、随机 Prim、Kruskal 并查集。它们生成的迷宫在分支密度、路径长度、视觉风格上都有区别。递归回溯生成的迷宫有明显的长走廊倾向一条主路能拐很多弯分叉少但方向转折多。随机 Prim 生成的迷宫中短分叉更多路径更均匀不会有特别长的连续通道。Kruskal 生成的结果和 Prim 相近但它天然适合处理“每个格子的墙是否要打通”这类无序合并操作逻辑上更贴近并查集。我优先推荐递归回溯原因有三个第一实现代码最短一个递归函数加一个方向随机洗牌就能搞定调试成本低第二生成的迷宫更适合演示寻路因为道路宽度统一、没有 2x2 的空白块视觉上更接近传统迷宫第三在 Qt 的网格模型里递归回溯只需要在一个二维数组上做标记不需要维护复杂的优先队列或并查集辅助数组。下面这张表是我在几种尺寸下对比后的选型结论供你参考算法迷宫风格主要数据结构实现难度适合场景递归回溯长走廊、转弯多栈递归/显式栈低演示、教学、迷宫游戏随机 Prim均匀、短分叉优先队列或集合中需要短路径较多的场景Kruskal均匀、结构松散并查集中高大规模生成、算法研究如果你觉得递归回溯生成的长走廊太像“蛇”优先考虑 Prim。但我个人建议第一版先用递归回溯跑通因为它的代码量小后面要改成 Prim 也只是换一个生成函数渲染和寻路都不受影响。2.2 用递归回溯生成随机迷宫最小可跑代码对递归回溯实现先把迷宫建模成“格子墙”模型假设迷宫是 rows 行 cols 列的格子cell每个格子有四堵墙。我们从左上角格子开始随机访问相邻格子只要相邻格子尚未访问就打通这两个格子之间的墙然后递归进去。这里有一个常见做法是直接在二维数组上做标记用 0/1 表示可以走的路和墙体而不是分别维护每个格子四面墙的布尔状态这样寻路部分会简单很多。生成迷宫的代码我用 C 搭配 Qt 的 QVector 写了一个最小版本。先定义一个MazeGenerator类核心函数如下// maze_generator.h #ifndef MAZE_GENERATOR_H #define MAZE_GENERATOR_H #include QVector #include QPoint #include queue #include random class MazeGenerator { public: // 1 表示可通行路径0 表示墙 QVectorQVectorint grid; int rows, cols; void generate(int r, int c) { rows r; cols c; // 初始化生成 (2*rows1) x (2*cols1) 的网格 // 奇数行和奇数列是格子本体偶数行/偶数列是墙 grid QVectorQVectorint(2 * rows 1, QVectorint(2 * cols 1, 0)); for (int i 0; i 2 * rows 1; i) for (int j 0; j 2 * cols 1; j) grid[i][j] (i % 2 0 || j % 2 0) ? 0 : 1; // 随机数生成器 std::mt19937 rng(std::random_device{}()); // 用显式栈模拟递归防止真正的递归过深 struct Step { int x, y; }; std::vectorStep stack; stack.push_back({1, 1}); grid[1][1] 1; while (!stack.empty()) { Step cur stack.back(); int x cur.x, y cur.y; // 收集相邻的、距离为 2 的未访问格子 QVectorQPoint neighbors; int dx[4] {2, -2, 0, 0}; int dy[4] {0, 0, 2, -2}; for (int k 0; k 4; k) { int nx x dx[k], ny y dy[k]; if (nx 1 nx 2 * rows ny 1 ny 2 * cols grid[nx][ny] 1) { neighbors.push_back({nx, ny}); } } if (neighbors.isEmpty()) { stack.pop_back(); } else { // 随机选一个邻居打通中间的墙 int idx rng() % neighbors.size(); QPoint nxt neighbors[idx]; int wallX (x nxt.x()) / 2; int wallY (y nxt.y()) / 2; grid[wallX][wallY] 1; grid[nxt.x()][nxt.y()] 1; stack.push_back({nxt.x(), nxt.y()}); } } } }; #endif // MAZE_GENERATOR_H这段代码的模型是这样的rows和cols表示可通行的格子数而grid的真实尺寸是(2*rows1) x (2*cols1)。我这样做的好处是迷宫的外墙和内部墙都统一表示成了网格里的0格子和墙体天然对齐渲染时只需要画一个二维数组的像素表不需要单独维护墙的坐标。dx/dy数组里的 2 和 -2 是“跨过一个墙”的步长/2索引就是两个格子中间的墙体坐标。需要注意的是grid[nx][ny] 1这个条件既是“未访问”判断也是“是否可通行格”判断。因为只有奇数行奇数列才初始化成 1所以邻居收集过程不会把墙当格子访问。如果你把std::random_device{}换成固定种子比如std::mt19937 rng(42);就能复现同一个迷宫这在调试寻路算法时很关键建议在类里留一个seed参数。2.3 迷宫网格的数据结构设计不用图论库也能存的邻接关系很多新手在生成迷宫之后想用 Dijkstra 或 A* 求路径于是开始建图、建邻接表。实际上完全没必要。我们这个二维数组本身就是图每个grid[i][j] 1的格子就是一个节点与它上下左右四个方向、值为 1 的格子就是相邻边行进代价可以统一看成 1。在 Qt 程序里我会把这个二维数组直接作为“逻辑地图”而不是转换成 QGraphicsScene 的元素后再搜路径。这样有两点好处路径搜索直接跑在原始数据上不用碰视图坐标渲染层只是把数组“翻译”成图形逻辑与显示解耦后续换渲染方案时不用改寻路代码。数据结构上我给一个小建议不要在MazeGenerator里塞 Qt 相关的类型进算法核心比如用QPoint存坐标没问题方便但如果你想把生成算法复用到纯命令行工具里就要把 QPoint 换成std::pairint,int。我一般视图和逻辑分两个类MazeGenerator只负责生成和暴露gridMazeView负责画出来这样单独对生成算法写单元测试也不依赖 Qt 环境。3. 路径获取A* 与 BFS 的取舍以及可视化方案迷宫生成完了下一步是“路径获取”。这个名字在 Qt 项目里一般包含两层意思一是从起点到终点找一条可达路径二是把这条路径以动画形式展示给用户。两个诉求对应两个技术选型搜索算法和定时驱动机制。3.1 为什么路径获取不选 DFS 而选 BFS/A*路径获取最朴素的实现是递归 DFS代码好写但它存在两个问题第一DFS 找到的路径不一定是最短的。迷宫是网格图权重均等BFS 能保证最短路径DFS 只是“能找到路”第二DFS 的搜索顺序依赖方向顺序如果方向排列固定那每次路径都一样视觉上很呆板。所以我在这个项目里一般不选 DFS 做最终路径展示最多拿它做验证用的“是否存在通路”。BFS 的实现复杂度低连优先队列都不需要一个std::queue就够。它按“层”扩散每扩散一层代表步数加 1记录每个格子由哪个格子扩展而来最后从终点回溯到起点就是最短路径。A* 在 BFS 基础上加了一个启发函数引导搜索方向网格规模小的时候比如 20x20两者性能差异不大但 A* 的搜索范围明显更小路径方向感更强演示效果更好。我在实际项目里会做一层抽象定义一个search(const QVectorQVectorint grid, QPoint start, QPoint end)接口内部实现可以是 BFS 也可以是 A*。界面上加一个下拉框让用户切换算法这样能看到两种算法扩展区域的差异。这个功能对做演示和答辩也很加分。3.2 A* 算法实现启发函数的选择和边界处理A* 的核心是每轮从优先队列里取出“当前代价 启发代价”最小的节点。对于网格迷宫当前代价取从起点到这个节点的步数启发代价取曼哈顿距离即当前节点到终点的横向距离加纵向距离。曼哈顿距离在只能上下左右移动的迷宫里面是“乐观估计”——它不会超过实际距离所以保证 A* 是可采纳的生成最短路径。直接上核心代码#include QVector #include QPoint #include queue #include unordered_map #include algorithm // 返回从 start 到 end 的路径包含两端 QVectorQPoint aStarPath(const QVectorQVectorint grid, QPoint start, QPoint end) { int rows grid.size(); int cols grid[0].size(); // 起点和终点合法性检查 if (start.x() 0 || start.x() rows || start.y() 0 || start.y() cols || end.x() 0 || end.x() rows || end.y() 0 || end.y() cols) return {}; if (grid[start.x()][start.y()] 0 || grid[end.x()][end.y()] 0) return {}; // gScore: 从起点到某格子的实际步数 QVectorQVectorint gScore(rows, QVectorint(cols, INT_MAX)); gScore[start.x()][start.y()] 0; // cameFrom: 记录每个格子是从哪个格子走来的用于回溯 QVectorQVectorQPoint cameFrom(rows, QVectorQPoint(cols, QPoint(-1, -1))); // 优先队列f g hpair 先比较 f再比较坐标 std::priority_queuestd::pairint, QPoint, std::vectorstd::pairint, QPoint, std::greater openQueue; openQueue.push({0, start}); int dx[4] {1, -1, 0, 0}; int dy[4] {0, 0, 1, -1}; while (!openQueue.empty()) { QPoint cur openQueue.top().second; openQueue.pop(); if (cur end) break; for (int k 0; k 4; k) { int nx cur.x() dx[k]; int ny cur.y() dy[k]; if (nx 0 || nx rows || ny 0 || ny cols) continue; if (grid[nx][ny] 0) continue; int tentativeG gScore[cur.x()][cur.y()] 1; if (tentativeG gScore[nx][ny]) { gScore[nx][ny] tentativeG; cameFrom[nx][ny] cur; int h std::abs(nx - end.x()) std::abs(ny - end.y()); openQueue.push({tentativeG h, QPoint(nx, ny)}); } } } // 回溯路径 if (cameFrom[end.x()][end.y()] QPoint(-1, -1)) return {}; QVectorQPoint path; QPoint step end; while (step ! start) { path.push_back(step); step cameFrom[step.x()][step.y()]; } path.push_back(start); std::reverse(path.begin(), path.end()); return path; }这段代码里有三个容易出错的点。第一个是cameFrom初始化为QPoint(-1, -1)这是“未访问”的哨兵回溯前必须检查终点有没有被访问否则如果无解会死循环。第二个是tentativeG gScore[nx][ny]这个判断它保证每个格子只被更优的路径更新如果写成也没错但可能会让路径选择倾向某些方向A* 的扩展顺序会不太一样。第三个是优先队列的std::greater必须用std::pairint, QPoint因为 pair 先比较 int 再比较 QPointC 标准库为 QPoint 提供了比较操作符所以这行能过编译。如果你想换 BFS只需要把启发函数h去掉优先队列换成普通std::queue逻辑其余部分完全一样。这就是我把搜索接口抽象出来的意义算法替换对上层透明视图层只管拿path画动画。3.3 用 QTimer 还是 QThread 做路径动画路径获取的结果是一串 QPoint 数组。如果直接一次性画到视图上用户看不到搜索过程只看到一条线突然出现那“获取路径”这个交互就少了很重要的反馈。我建议把路径逐格点亮形成“蚂蚁爬行”的动画效果。第一个反应可能是开一个线程跑循环每步QThread::msleep(30)然后发信号给界面更新。这个方案翻车概率很高因为QThread::msleep阻塞的是工作线程更新界面还需要跨线程发信号加上线程生命周期管理代码量陡增。简单场景完全没必要。更省事的是用QTimer设定interval30每次超时走一步路径走到终点后停止定时器。代码大概是这样// 视图类里的槽函数 void startPathAnimation(QVectorQPoint path) { m_path path; m_stepIndex 0; m_timer-start(30); // 30ms 走一步 } void onTimerTick() { if (m_stepIndex m_path.size()) { m_timer-stop(); return; } // 高亮 m_path[m_stepIndex] 对应的格子 highlightCell(m_path[m_stepIndex]); m_stepIndex; }这个方案唯一的注意点是highlightCell里不要做大数据量的重绘只更新当前帧涉及的元素。如果迷宫的 view 是用QGraphicsScene实现的我会在启动动画前把整条路径用“浅色画一遍”作为底衬然后用一个单独的“高亮笔刷”逐段加深这样视觉上既保持了完整性又实现了动画效果。使用QTimer还有一个额外好处用户随时可以点击按钮触发stop()对交互控制非常友好。4. Qt 绘图架构QGraphicsView 还是 QPainter别选错写完算法你面临一个现实问题迷宫和路径怎么在 Qt 界面上画出来。这部分我见过最多翻车的骚操作有人用 QLabel 加 setPixmap 逐像素生成图片迷宫一变大就卡有人在 QWidget::paintEvent 里用 QPainter 把所有格子重新画一遍每次动画都整表重绘还有人一开始用 QGraphicsView但坐标换算一直没搞清最后又把所有代码推翻。所以我单独拿出一章讲选型。4.1 两套绘图方案的性能对比和适用边界Qt 里画迷宫主要有两条路QPainter直接画在 QWidget 上或者QGraphicsView / QGraphicsScene搭建场景。对于静态迷宫两者都能胜任对于带寻路动画、支持鼠标点击编辑、未来可能要拖拽缩放的地图编辑器QGraphicsView 明显更好。因为QGraphicsItem自带图元坐标、碰撞检测和事件分发你不需要自己算哪个像素属于哪个格子。当然 QPainter 也不是不能用。如果你的迷宫固定 20x20不需要缩放不想引入 QGraphicsView 的坐标体系那 QPainter 写法最直接。paintEvent 里用双重循环按格子尺寸画填色方块代码量很少。可它有两个硬伤一是动画时每次 update() 会重绘所有格子格子一多超过 100x100就明显掉帧二是要自己做“像素坐标到格子坐标”的换算点击事件和绘制逻辑耦合在一起后面扩展编辑器时很痛苦。我做这个项目时直接上了 QGraphicsView不是因为它新而是因为它把很多后续需求天然解决了。下面这张表可以帮你做决定需求QPainterQGraphicsView静态迷宫推荐可以但略重寻路逐格动画勉强小规模推荐鼠标点击编辑格子需手写坐标换算图元自带事件滚轮缩放 / 拖动需手动实现内置支持大规模迷宫100x100重绘压力大局部更新更占优如果你只做课程设计迷宫固定 15x15我建议你选 QPainter省时间。如果像我一样做完生成和寻路还想加手动编辑、导出地图、起点终点拖拽那 QGraphicsView 可以让你少走很多弯路。4.2 用 QGraphicsView 搭建迷宫场景最小交互框架用 QGraphicsView 画迷宫常见做法不是给每个格子单独创建一个 QGraphicsRectItem那样 50x50 的迷宫就有 2500 个图元内存和创建时间都浪费。我一般用一个QGraphicsPixmapItem或一个自定义QGraphicsItem一次性绘制整张迷宫位图再把起点、终点、寻路路径做成单独的图元叠加在上面。这里给出最小框架class MazeView : public QGraphicsView { Q_OBJECT public: MazeView(QWidget* parent nullptr) : QGraphicsView(parent) { m_scene new QGraphicsScene(this); setScene(m_scene); setRenderHint(QPainter::Antialiasing); } void setMaze(const QVectorQVectorint grid) { m_scene-clear(); int rows grid.size(); int cols grid[0].size(); int cellSize 20; // 生成整张迷宫图片一次画进场景 QImage mazeImage(cols * cellSize, rows * cellSize, QImage::Format_RGB32); QPainter p(mazeImage); p.fillRect(mazeImage.rect(), Qt::white); for (int i 0; i rows; i) { for (int j 0; j cols; j) { if (grid[i][j] 0) { p.fillRect(j * cellSize, i * cellSize, cellSize, cellSize, Qt::black); } } } p.end(); QGraphicsPixmapItem* mazeItem m_scene-addPixmap(QPixmap::fromImage(mazeImage)); mazeItem-setFlag(QGraphicsItem::ItemIsSelectable, false); // 让场景大小和迷宫一致防止拖动后出现空白区 setSceneRect(mazeImage.rect()); } private: QGraphicsScene* m_scene; };这个方案的要点在于把迷宫本身合成一张位图而不是创建上千个矩形图元。原因有二一是大图一次性绘制比上千次小图元绘制快很多二是寻路动画只需要在迷宫图上叠加一个“高亮路径图元”不必操作底层迷宫图元。你以后想给迷宫加“网格线”效果也只需要在同一张位图里多画几笔线条不会增加图元数量。关于cellSize这个参数我多说一句它直接决定迷宫在界面上的像素尺寸。20 像素对 20x20 的迷宫约 400x400够看对 50x50 的迷宫就到了 1000x1000屏幕小的设备上需要配合setDragMode(ScrollHandDrag)或缩放。我一般会把cellSize做成一个公开属性并在界面放一个 SpinBox 控制它用户生成前可以调节显示粒度。4.3 键盘控制角色移动与碰撞检测路径获取之后很多项目会顺带加一个“角色试走”的功能用户用键盘上下左右控制一个小方块从起点走到终点走到终点弹窗提示通关。这个功能在 QGraphicsView 下做非常简单也能让程序看上去更像一个真正的游戏而不是算法演示。角色可以是一个QGraphicsEllipseItem位置用setPos控制。键盘事件在keyPressEvent里处理每按一次方向键先计算目标格子坐标检测该格子是不是grid里的可通行格子再决定是否移动。完整代码如下void MazeView::keyPressEvent(QKeyEvent* event) { if (!m_playerItem) return; // 获取当前角色所在格子 int row m_playerRow; int col m_playerCol; int dx 0, dy 0; switch (event-key()) { case Qt::Key_Up: dx -1; break; case Qt::Key_Down: dx 1; break; case Qt::Key_Left: dy -1; break; case Qt::Key_Right: dy 1; break; default: QGraphicsView::keyPressEvent(event); return; } int newRow row dx; int newCol col dy; if (newRow 0 || newRow m_grid.size() || newCol 0 || newCol m_grid[0].size()) return; if (m_grid[newRow][newCol] 0) return; // 撞墙 // 更新角色位置 m_playerRow newRow; m_playerCol newCol; m_playerItem-setPos(newCol * m_cellSize, newRow * m_cellSize); if (newRow m_endRow newCol m_endCol) { QMessageBox::information(this, 完成, 角色到达终点); } }碰撞检测完全基于m_grid的逻辑地图而不是基于图形项的碰撞这样做的最大好处是精准、零误差、不依赖视觉位置。这一段代码的核心在于网格坐标和像素坐标的换算用m_cellSize统一完成角色只有在逻辑地图上“合法移动”视觉才会跟着走。5. 避坑Qt随机迷宫及路径获取的六个常见问题这部分是我在这个项目上翻车的集中记录。每一条都符合“现象 → 原因 → 解决”的结构你可以对照自己的报错和异常行为来判断。5.1 递归生成迷宫时栈溢出现象迷宫尺寸提高到 50x50 甚至 100x100 后程序直接崩溃退出调试器显示栈溢出。原因递归回溯算法在递归实现时每个格子的调用都占用一层栈一条很长的路径可能让递归深度达到几十万层。解决把递归改成显式栈就是我前面MazeGenerator里的写法。C 的函数调用栈不适合承担这种深度未知的递归用std::vector模拟栈既安全又能完全控制顺序。5.2 画出来的迷宫总是整体偏移一个格子现象迷宫外墙有一边没有被渲染或者道路和墙壁错位整体看得出图案扭曲。原因生成算法里grid的尺寸是(2*rows1)而显示时按rows x cols遍历少画了最外圈。解决显示遍历必须用grid.size()和grid[0].size()不要用rows和cols。这是一个非常容易犯的边界错误根源在于生成和渲染两套尺寸概念没有统一。5.3 路径动画卡顿界面无响应现象点击寻路按钮后界面卡住一两秒动画直接跳到终点没有逐步效果。原因很可能在槽函数里用了for循环连着update()或者在子线程里直接调用了repaint()。Qt 的窗口重绘不是立即发生的连续调用 update 只会合并成一次。解决用QTimer驱动动画每次只推进一个格子。这样事件循环有机会处理其他 UI 消息动画才流畅。5.4 每次生成迷宫一模一样现象点了很多次生成按钮迷宫结构完全相同。原因随机数生成器采用固定种子或者用了qsrand(0)初始化。解决默认使用std::random_device作为种子并且提供一个手动设置种子的接口用于复现特定地图。随机性在调试时是敌人在演示时是能力所以种子控制一定要暴露出来。5.5 迷宫外墙渲染不闭合“漏风”现象最外面一圈墙某些位置是空白角色能从边界的缺口走出去。原因生成时只考虑了奇数行列格子的内部最外圈墙体没有初始化或者初始化成了 1。解决生成初始化时把四个边界都设为 0也就是墙。我在MazeGenerator里初始化时偶数行偶数列全部是 0这一步已经覆盖边界但如果你是从别的博客抄的简化版非常容易漏掉这个细节。5.6 QGraphicsView 缩放后点击坐标不准现象使用滚轮缩放场景后鼠标点击格子选中的位置和实际位置有偏差。原因没有用mapToScene把视图坐标转换为场景坐标直接读了event-pos()。解决一律用QPointF scenePos mapToScene(event-pos());再做像素到格子的换算。同理用setSceneRect锁定场景范围能避免拖拽后出现空白区域。6. 进阶技巧把迷宫扩展成可编辑的地图编辑器到这里基础功能已经完整随机生成、路径获取、键盘操作。最后一章讲一个进阶方向把程序升级成“迷宫地图编辑器”。这样原来只能看随机结果的项目变成了用户可以手动设计关卡的工具实用性直接上一个台阶。做法很简单核心是把MazeGenerator的grid暴露出来然后在视图的鼠标点击事件里做格子状态翻转。要点是像素坐标换算成格子坐标再对格子执行grid[newRow][newCol] 1 - grid[newRow][newCol]。鼠标左键把墙变路右键把路变墙。起点和终点可以用拖拽方式设置或者用两个按钮来指定。因为grid是大家在生成和寻路之间共享的唯一数据源所以编辑完成后直接调用路径获取函数就能立即看到新效果。你需要注意编辑器模式下要限制用户不能把起点或终点的格子改成墙这需要在翻转前做一次合法性判断。做法是在grid之外再维护一个QPoint start, end翻转时先检查newRow start.x() newCol start.y()如果是就直接忽略。类似地外墙边界不允许编辑避免再次出现“漏风”问题。还有一个习惯我想强烈推荐在扩展功能前写一个自动验证脚本。用同样的grid分别跑 BFS 和 A*断言两条路径的长度一致再随机生成一百张迷宫手动随机设起点终点检查“有解”和“无解”两种情况的返回值是否正常。这样你加任何功能都不怕把寻路算法改坏。我是在一次大规模重构时把这个习惯补上的从那以后改代码的胆子大了一倍。我把这个编辑器做完之后最大的体会是算法本身并不复杂复杂的是把算法嵌入到一套 GUI 框架时产生的各种约束。作为一线编码者我更关心“界面操作是否即时响应”“动画会不会卡”“边界条件是否可靠”而这些问题只有踩过坑才能形成直觉。希望这篇文章能让你在实现 Qt随机迷宫及路径获取 时少踩几个同样的坑直接用一个能跑、能改、能演示的框架起步把时间花在真正想做的事情上。希望帮到你。本文还有配套的精品资源点击获取