简介:基于QT实现的地图导航系统,是面向数据结构课程设计或毕业设计场景的完整C++项目源码包。系统以Dijkstra算法为核心,结合QT图形界面完成地图展示、路径规划与导航交互,适合需要完成课设作业、理解最短路径算法落地实现或学习QT界面开发的学生参考。资源包共34个文件,压缩后大小约16.91MB,包含8个cpp源码、7个h头文件、4个ui界面文件以及qrc资源文件、pro工程文件和png/jpg图片素材,涵盖登录、民族风情展示、轮播图、地图导航等模块,结构完整可直接打开工程运行。已有229人学习下载。通过这套代码,读者可以查看完整的类划分与界面逻辑,对照Dijkstra算法在真实地图数据上的应用方式,还能复用其中图片、音频等资源快速搭建演示原型,尤其适合课程答辩或功能演示前的准备。
1. 为什么数据结构课设总有人选“地图导航”:一个QT项目把图论讲活了
数据结构课设最怕两件事:一是选题太理论,写出来全是概念堆砌,答辩时老师一句“你这里为什么用数组不用链表”就能问哑火;二是选题太工程,比如做个图书管理系统,和数据结构核心没半点关系。而“基于QT实现的地图导航系统(Dijkstra算法)”恰好踩在中间:图的存储、遍历、最短路径、工程化界面全都有,做完既能在答辩时讲清楚数据结构设计,又能拿出一个看得见摸得着的导航界面。这个项目最基本的形态是:用QT画一个城市地图(节点+道路),用户点击起点和终点,系统用Dijkstra算法算出最短路径并在界面上高亮显示。
这个项目适合三类人:正在准备数据结构课设的本科生、想复习图论并把算法落地成代码的考研党、以及想用一个小项目上手QT的初学者。它不需要你懂复杂的GIS(地理信息系统),不需要真实地图数据,用一张手画的抽象地图就能完整展示Dijkstra算法的运行过程。我见过很多同学把项目做成了“QT界面+算法背答案”的拼接体——界面是界面,算法是算法,两者不沟通。这篇文章要解决的就是让你从数据结构设计开始,把QT界面和Dijkstra算法真正缝合在一起,跑出一个能演示、能答辩、能扩展的完整系统。
2. 地图导航系统的数据结构选型:邻接矩阵还是邻接表,不能拍脑袋
2.1 从“图”说起:地图导航系统里的节点和边到底是什么
地图导航系统的底层模型是一张带权无向图(如果道路是单行道就是有向图)。这里的节点是地标或路口,边是道路,边的权值是距离、通行时间或综合代价。数据结构课设里最核心的评分点就在这个模型上:你能不能说清楚图用什么结构存、为什么这么存、时间和空间复杂度各是多少。
我一般会先把地图抽象成两个数组:一个存节点信息(编号、名称、屏幕坐标),一个存边信息(起点、终点、权值)。节点信息结构体大概长这样:
struct MapNode { int id; // 节点编号,0起 QString name; // 地点名称 double x, y; // 在QT画布上的坐标 }; struct MapEdge { int fromId; int toId; int weight; // 距离或时间代价 };为什么单独定义边结构体而不直接塞进邻接矩阵?因为课设里你需要支持“用户在线编辑地图”这类加分功能——如果地图是写死在代码里的,答辩时老师让你加一个节点,你就要改源码重编译,观感很差。把边和节点作为独立数据,运行时就能动态增删。这里用QString是因为QT项目直接用Qt库,别自找麻烦用std::string再转来转去。
2.2 邻接矩阵实现:代码简单但代价明确
邻接矩阵是二维数组,matrix[i][j]表示节点i到节点j的边权值,没有边就填一个足够大的数(比如INT_MAX / 2,不能直接填INT_MAX,后面Dijkstra做加法会溢出)。节点数少(几十个)时我会直接用邻接矩阵,因为代码简单,答辩时好解释。
const int MAX_NODES = 100; const int INF = INT_MAX / 2; class GraphModel { public: int nodeCount; QVector<MapNode> nodes; int adjMatrix[MAX_NODES][MAX_NODES]; void init(int n) { nodeCount = n; for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) adjMatrix[i][j] = (i == j) ? 0 : INF; } void addEdge(int u, int v, int w) { adjMatrix[u][v] = w; adjMatrix[v][u] = w; // 无向图对称赋值 } };注意INF = INT_MAX / 2这个细节:如果直接填INT_MAX,运行dist[u] + w时一旦dist[u]本身是无穷大,加一个正整数就溢出变成负数,Dijkstra的松弛操作直接翻车。除以2虽然不严谨,但足够应付课设规模的数据。
邻接矩阵的空间复杂度是 O(n²),100 个节点就是 1 万个 int,40KB 左右,对现代计算机来说是零头。但当节点数超过 500,矩阵会膨胀到 1MB 以上,如果地图有几千个节点,就必须换邻接表。
2.3 邻接表实现:用QVector模拟链表,避免手动指针
如果地图节点多且稀疏(大部分节点之间没有直接道路),邻接表更合适。用C++手写链表容易出内存泄漏,课设项目里我推荐用QVector<QVector<QPair<int, int>>>模拟邻接表,既保留“链式存储”的数据结构讲解价值,又不需要手动管理指针。
class GraphModel { public: QVector<QVector<QPair<int, int>>> adjList; // pair<目标节点, 权值> void init(int n) { adjList.clear(); adjList.resize(n); } void addEdge(int u, int v, int w) { adjList[u].append(qMakePair(v, w)); adjList[v].append(qMakePair(u, w)); } void removeEdge(int u, int v) { auto it = std::remove_if(adjList[u].begin(), adjList[u].end(), [v](const QPair<int, int>& e) { return e.first == v; }); adjList[u].erase(it, adjList[u].end()); // 同样处理 adjList[v] } };QVector在这里替代了链表节点,但它在内存上是连续存储的,严格说不是标准邻接表。答辩被问到可以说:这是“用顺序容器模拟链式结构”,实际生产环境会用std::list或手写链表;课设场景下QVector的随机访问特性反而能简化Dijkstra中遍历邻居的代码。面试或考研笔试时还是答真链表版本。
我的建议是:课设节点少于200个就无脑用邻接矩阵,理由有三——代码量少、答辩容易讲、Dijkstra的双重循环天然适合数组下标访问。如果你做的地图是那种“城市道路网络”模拟,节点多但稀疏,再上邻接表。两者代码我都给你了,选一个即可,不要在数据结构的“最佳实践”上纠结太久,课设的核心是把Dijkstra跑通。
2.4 边的权值怎么定:距离是伪命题,时间才是常态
真实导航系统的代价不是物理距离,而是通行时间。但课设不需要那么复杂,你只需要定一个规则:权值 = 两点屏幕坐标的欧几里得距离取整。这样地图上看起来近的点,Dijkstra算出来就近,直观且答辩好解释。
int calWeight(const QPoint& p1, const QPoint& p2) { double dx = p1.x() - p2.x(); double dy = p1.y() - p2.y(); return (int)(std::sqrt(dx*dx + dy*dy) + 0.5); // 四舍五入 }注意一条路上的权值是双向的(无向图),有向图的场景是单行道,这个看你的地图设计。还有一点:权值如果是整数,Dijkstra用int数组就够;如果想要更“真实”的导航效果,可以改成double存储时间 = 距离 / 速度。但会增加浮点比较的麻烦,课设里我用int,理由很简单——演示效果没有差异,代码更不容易出bug。
3. Dijkstra算法落地:从“背模板”到“能讲清楚每一步”
3.1 算法核心逻辑与代码框架:权值为正才成立
Dijkstra算法解决的问题是:给定起点 s,求到图中所有其他节点的最短路径权值和,以及路径经过哪些节点。它适用于边权非负的图——这是它区别于Bellman-Ford的核心前提,也是答辩必考题。算法的核心理念是贪心:每次从未访问节点中选一个离起点最近的点,标记为已确定最短路径,然后用它去松弛它的邻居。
QVector<int> dijkstra(int src, QVector<int>& pre) { int n = nodeCount; QVector<int> dist(n, INF); QVector<bool> visited(n, false); pre.fill(-1); dist[src] = 0; for (int iter = 0; iter < n; ++iter) { // 1. 选未访问的最小dist节点 int u = -1; int minDist = INF; for (int i = 0; i < n; ++i) { if (!visited[i] && dist[i] < minDist) { minDist = dist[i]; u = i; } } if (u == -1) break; // 剩余节点全不可达 visited[u] = true; // 2. 松弛u的所有邻居 for (int v = 0; v < n; ++v) { if (!visited[v] && adjMatrix[u][v] < INF) { if (dist[u] + adjMatrix[u][v] < dist[v]) { dist[v] = dist[u] + adjMatrix[u][v]; pre[v] = u; // 记录前驱,用于回溯路径 } } } } return dist; }这段代码是“未优化版”,时间复杂度 O(n²)。为什么不用优先队列(堆优化版)?原因很实际:课设地图就几十个节点,O(n²) 和 O(E·logV) 的运行时间差异用肉眼看不出来,但 O(n²) 版代码短一半,答辩时讲思想更顺畅——你只需要说“这里每次用线性扫描找最小dist节点”,而不是解释堆为何能优化。如果老师追问堆优化版本,你口述“用priority_queue维护候选节点,弹出时跳过过期记录”就能过关;真要写出来,优先级队列里存的 pair<dist, node> 记得greater<>才是小顶堆。
pre数组是回溯路径的关键:它在松弛时记录“从哪个节点过来更近”。最终回溯路径的代码:
QVector<int> getPath(int src, int dst, const QVector<int>& pre) { QVector<int> path; for (int cur = dst; cur != -1; cur = pre[cur]) { path.append(cur); if (cur == src) break; } std::reverse(path.begin(), path.end()); return path; }注意for循环的终止条件是cur != -1,但理论上前驱链一定会在src处断掉。如果你发现路径不对,先打印pre数组看看哪里没被正确赋值。我见过太多同学路径回溯不出来,结果发现是从dst往src追的时候追到了死胡同——因为pre[src] = -1,循环条件一开始就退出,导致只能存下一个节点。
3.2 优先队列优化:代码就多了十行,但复杂度对得上
如果节点数上了 500,或者你在地图里加了“城市路网”的复杂模拟,线性扫描的 O(n²) 会明显卡顿。堆优化版的核心改动只有两处:用priority_queue替代线性扫描,用continue跳过已经过期的队列元素。
QVector<int> dijkstraHeap(int src, QVector<int>& pre) { int n = nodeCount; QVector<int> dist(n, INF); QVector<bool> visited(n, false); pre.fill(-1); using P = QPair<int, int>; // <当前距离, 节点id> priority_queue<P, QVector<P>, std::greater<P>> pq; dist[src] = 0; pq.push(qMakePair(0, src)); while (!pq.empty()) { int d = pq.top().first; int u = pq.top().second; pq.pop(); if (visited[u]) continue; // 过期记录,跳过 if (d > dist[u]) continue; // 双保险 visited[u] = true; for (int v = 0; v < n; ++v) { if (!visited[v] && adjMatrix[u][v] < INF && dist[u] + adjMatrix[u][v] < dist[v]) { dist[v] = dist[u] + adjMatrix[u][v]; pre[v] = u; pq.push(qMakePair(dist[v], v)); } } } return dist; }std::greater<P>是小顶堆的正确姿势,写std::less就变成大顶堆,算法会算出一个完全错误的最短路。这个坑我见过不止一次,代码单个看没问题,跑起来结果就是错的,还找不到原因。用priority_queue配合QVector作底层容器是可行的,因为std::priority_queue只要容器支持push_back、pop_back、front,QVector都满足。答辩如果问为什么不用QPriorityQueue,QT没这个东西,直接用STL的即可。
3.3 路径可视化:把算法结果画到QT界面上不只是一条线
算出来的路径是一串节点id的数组,例如[0, 3, 7, 12]。要把这串数字变成地图上一条可见的高亮路线,需要在QWidget的paintEvent里画图。这里的关键是区分“普通边”和“最短路径边”——前者灰色细线,后者红色粗线。
void MapWidget::paintEvent(QPaintEvent*) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); // 画所有普通边 QPen normalPen(Qt::gray); normalPen.setWidth(2); painter.setPen(normalPen); for (const auto& e : edges) { QPoint p1 = nodePos[e.fromId]; QPoint p2 = nodePos[e.toId]; painter.drawLine(p1, p2); } // 画最短路径 if (!pathIds.isEmpty()) { QPen pathPen(Qt::red); pathPen.setWidth(4); painter.setPen(pathPen); for (int i = 0; i < pathIds.size() - 1; ++i) { painter.drawLine(nodePos[pathIds[i]], nodePos[pathIds[i+1]]); } } // 画节点(圆形+名称) for (const auto& node : nodes) { painter.setBrush(Qt::white); painter.setPen(Qt::black); painter.drawEllipse(node.pos, 10, 10); painter.drawText(node.pos + QPoint(-15, -15), node.name); } }不要每画一个元素就设置一次Pen,会很卡。QPainter的Pen切换是性能杀手,尽量把相同样式的绘制集中在一起。绘制结束后调用this->update()触发重绘——这是QT里经典的“改了数据但界面不刷新”问题的解法。
还有一个体验细节:Dijkstra计算是瞬时的,但教学演示时让“路径被一条一条画出来”更有冲击力。用QTimer每一帧只新增一条边,从起点开始逐渐延伸到终点。这个动画效果本身不会加分(因为算法复杂度没变),但会让你在答辩现场明显更受关注——老师会觉得你考虑了用户体验环节。
4. QT界面与算法缝合:选择QGraphicsView而不是直接画,省掉一半的坐标计算
4.1 为什么我不用paintEvent硬画,而是选QGraphicsView
很多初学者直接在QWidget的paintEvent里画节点和边,节点少的时候没毛病,但一旦想要加“鼠标拖拽节点”“点击选中”“缩放平移”这些功能,坐标计算会让人崩溃。用QGraphicsView + QGraphicsScene是QT官方推荐的图形框架,它自带图元(Item)的概念,每个节点和边都是一个独立对象,能响应鼠标事件和选中状态。
class NodeItem : public QGraphicsEllipseItem { public: int id; NodeItem(int id, QPointF pos, QString name) : QGraphicsEllipseItem(-10, -10, 20, 20), id(id) { setPos(pos); setBrush(Qt::white); setFlag(QGraphicsItem::ItemIsMovable); // 用QGraphicsTextItem添加名称标签 } };这个设计的精髓在于:节点坐标由setPos统一管理,paintEvent里手动维护的nodePos数组可以删掉。画边的时候直接从nodeItem->pos()取坐标,拖拽节点后边会自动连着走,因为每次重绘都会从最新的pos取点。
用QGraphicsView的另一个好处是缩放平移是开箱即用的——view->setDragMode(QGraphicsView::RubberBandDrag)配合滚轮事件就能实现地图缩放。这对“地图导航”这个主题是一个很大的加分项,因为真实地图产品都有缩放功能。而且你不用自己处理坐标变换:界面上鼠标点击的位置,通过mapToScene自动变成场景坐标。
4.2 鼠标交互:点击起点终点、拖拽地图、右键弹出菜单
导航系统最基础的交互有两个:点击节点设置起点/终点,右键拖拽平移地图。用QGraphicsView实现这些事件要用mousePressEvent和mouseMoveEvent,不要用QMouseEvent的全局坐标,要转换成场景坐标。
void MapView::mousePressEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) { QPointF scenePos = mapToScene(event->pos()); NodeItem* item = dynamic_cast<NodeItem*>(scene()->itemAt(scenePos, QTransform())); if (item) { if (startNode == -1) { startNode = item->id; item->setBrush(Qt::blue); } else if (endNode == -1) { endNode = item->id; item->setBrush(Qt::red); computeAndDrawPath(); // 起点终点都有了就自动跑Dijkstra } } } QGraphicsView::mousePressEvent(event); }scene()->itemAt(scenePos, QTransform())是“判断鼠标点在哪个图元上”的标准写法,这里的QTransform()参数是空变换,表示用场景坐标直接查。用qgraphicsitem_cast或dynamic_cast来确认点中的是节点而不是边,避免误触。点击顺序是“先起点后终点”,这个逻辑需要用状态变量维护,建议是startNode == -1时设置起点,否则设置终点。如果你想支持“重新选起点”,就把点击起点的逻辑改为“点击任意节点直接设起点,再次点击设终点”,不要一开始就写成只能按顺序点。
4.3 用QTableWidget做节点编辑区:增删节点和边的快捷入口
光有鼠标交互不够,课设还要求“可维护性”——能让用户动态编辑地图。我的做法是在窗口右侧放一个QTableWidget,三列:操作按钮(添加节点/添加边/删除)、节点列表、边的列表。界面不复杂,但能把地图数据结构和QT的信号槽机制串起来。
connect(addNodeBtn, &QPushButton::clicked, this, [=]() { // 在场景正中心添加一个可拖拽的新节点 int newId = graph.nodes.size(); graph.nodes.append(MapNode{newId, QString("Node%1").arg(newId), QPointF(0, 0)}); NodeItem* item = new NodeItem(newId, QPointF(0, 0), graph.nodes.last().name); scene->addItem(item); refreshNodeTable(); // 更新右侧表格 });添加节点时别忘了一并更新GraphModel的邻接矩阵或邻接表,还要刷新视图。删除节点时要注意:删除一个节点要把所有和它关联的边一并删除,否则边上会挂在已不存在的节点id上,Dijkstra算法遍历时会越界或读脏数据。这就是课设里最常见的“删了个节点,导航就崩溃”的坑——不是QT的问题,是你数据结构不同步的问题。
5. 避坑/常见问题/排查:二十二个“我见过最多人翻车”的地方
问题1:点击运行,窗口黑屏只有空白
- 现象:程序编译通过、能启动,但界面全白或空荡荡。
- 原因:没有设置
scene->setSceneRect,QGraphicsView不知道场景的边界,或者根本没有把item添加到scene里。 - 解决:在初始化时调用
view->setScene(scene)并设置scene->setSceneRect(0, 0, 800, 600);添加item后调用scene->addItem(item)。检查这两步是否都执行了。
问题2:Dijkstra算出的路径是空的,但明明两个节点有通路
- 现象:点击终点后毫无反应,或者路径只显示一个点。
- 原因:路径回溯代码有bug。最常见的是
pre数组没有正确记录前驱,或者在循环里用了break导致起点没被回溯到。 - 解决:先在调试器里看
pre数组的赋值情况——从终点往前追,打印每一步的节点id。确认松弛操作里的pre[v] = u确实是在dist[v]更新时执行的。不要把这个赋值放在if外面。
问题3:QT编译报错:-1: error: dependent '..\..\..\..\..\..\qt\5.15.2\msvc2019_64\include\qtwidgets' does not exist
- 现象:这句错误带有乱码一样的路径,指向你自己项目目录之外的路径。
- 原因:QT的构建系统(qmake或CMake)在查找QT库时,使用了环境变量的错误路径。常见于手动设置过
QTDIR,或者QT版本/编译器位数(msvc2019_64 vs mingw)和自己安装的不匹配。 - 解决:检查系统环境变量
QTDIR是否指向正确的QT安装路径(例如C:\Qt\5.15.2\msvc2019_64)。如果是用QT Creator,在“工具→选项→Kits”里确认编译器、调试器、QT版本对应。重装QT或更改环境变量后记得重启QT Creator。
问题4:拖拽节点后,边没有跟着一起移动
- 现象:节点拖到新的位置,但边还留原来的位置,画面看起来断裂。
- 原因:你在
paintEvent里用的是自己维护的坐标数组,而不是从节点item实时取pos()。 - 解决:所有画边的地方都要从对应的节点item取坐标,比如
QPointF p1 = nodeItemMap.value(edge.fromId)->pos()。不要在别处保存一份“曾经有效的坐标”。
问题5:程序启动白屏,但IDE输出“QBackingStore::endPainting: Painter active on backingstore”告警
- 现象:控制台有警告,界面闪烁或白屏。
- 原因:在
paintEvent里手动调用了QPainter::begin()/end(),或者用QPainter时事件循环尚未就绪。 - 解决:不要在
paintEvent之外的地方构造QPainter。使用QGraphicsView时完全不建议在paintEvent里手动绘制,改用QGraphicsScene和Item会让这个错误消失。
问题6:节点一多,拖动时画面卡顿
- 现象:地图上有上百个节点,拖动一个节点时其他node和edge重绘明显掉帧。
- 原因:所有item的
boundingRect相交,导致每次重绘时刷新的区域过大。更常见的是边item的边界计算写成了无限大。 - 解决:为边item设置精确的
boundingRect(一条线的边界就是线路经过的最小矩形,可以用QRectF存储)。另外不要给每个边都单独添加一个item,几十条线的场景直接用一个QGraphicsPathItem一次画完,性能会好一个量级。
问题7:用优先队列优化版Dijkstra时,路径长度不对但又不是全部错误
- 现象:部分节点的最短路径算对了,部分错了,而且错得很奇怪。
- 原因:
priority_queue的声明写错了比较器。std::greater要套在pair上,如果只写greater不指定类型,会编译报错;如果写less,就成了大顶堆,每次弹出的都是“当前最远”的节点,越弹越错。 - 解决:检查
using P = QPair<int, int>; priority_queue<P, QVector<P>, std::greater<P>> pq;这两个模板参数是否完整。
问题8:Dijkstra函数里访问了越界下标
- 现象:在debug模式下程序崩溃,release模式下偶尔崩。
- 原因:图数据里存在非法边——比如删除节点时没有同时删除关联边,残留了“指向已经不存在节点”的边。
- 解决:在删除节点时,手动遍历边的列表并移除所有和该节点相关的边。我一般在
GraphModel::removeNode里加一个removeEdgeIfContains的辅助函数,把清理逻辑收拢。
问题9:地图上有两个完全独立的子图,起点和终点不在同一子图
- 现象:起点终点都选了,但路径面板显示“无法到达”。
- 原因:这是正常现象,但需要给用户反馈。Dijkstra结束后
dist[target] == INF就说明不可达。 - 解决:不要只弹一个空窗口,要弹
QMessageBox::information(this, "提示", "起点和终点不在同一连通区域")。这个提示会让答辩显得你考虑过异常情况,加分。
问题10:QT 5.15.2 下QString::arg和std::to_string混用导致乱码
- 现象:界面上的地名变成了乱码或空字符串。
- 原因:
QString默认使用UTF-16,和std::string的转换需要显式编码声明。直接QString(str.c_str())在某些编码下会乱码。 - 解决:用
QString::fromUtf8(str.c_str())或直接用QString("Node%1").arg(id)构建,不要从中文字符串和STL字符串之间来回倒。
6. 让课设从及格变优秀的三个小技巧,以及一个保底的验收自测
6.1 给Dijkstra加一个“动画慢放”模式:不只演示结果,演示过程
默认点击终点后直接高亮路径,30秒结束答辩演示,老师无感。加一个“逐步演示”开关,算法每确定一个节点的最短距离,就在界面上用一个高亮圆点标记,并用QTimer控制节奏(比如500ms走一步)。这个功能不需要改算法核心,只需要在Dijkstra的循环里加一个信号发射:
emit stepFinished(currentNode, dist[currentNode]);然后在界面槽函数里把这个节点的颜色改成已访问色,顺带在状态栏显示当前距离值。这个改动大概半小时工作量,但对答辩效果的影响非常大——老师会认为你“真的理解了算法过程”,而不仅仅是调用了一个库函数。
6.2 输出路径方案的文本说明:解释每一步的选择
答辩时最怕老师问“为什么走这条不走那条”。给系统加一个“路径细节”文本窗,点击显示:
从 图书馆 到 食堂: 经过节点: 图书馆(0) -> 教学楼(3) -> 食堂(7) 总距离: 1200m这个输出在代码里就是从pathIds数组循环拼字符串,但它的价值在于回答了答辩中几乎必被追问的“说说这个路径怎么算出来的”。我还会在这个文本窗里附带每个节点的距离表,展示Dijkstra逐步更新的过程,这会让答辩记录里多一条“对算法理解深入”的评价。
6.3 保底自测:一段代码验证算法在极端输入下不崩
课设项目交上去之前,我建议你写一个临时测试函数,把下面这几个场景全部跑一遍,确保没有隐藏的崩溃点:空图选起点终点、单节点图、起点等于终点、不可达路径、删除当前选中的起点后再跑Dijkstra、连续快速双击起点终点。
void selfTest() { GraphModel g; g.init(1); // 单节点 auto dist = g.dijkstra(0, dummyPre); Q_ASSERT(dist[0] == 0); g.init(2); // 没有边 dist = g.dijkstra(0, dummyPre); Q_ASSERT(dist[1] == INF); // 起点等于终点 QVector<int> pre; dist = g.dijkstra(0, pre); QVector<int> path = g.getPath(0, 0, pre); Q_ASSERT(path.size() == 1 && path[0] == 0); }这段自测代码在开发过程中跑一遍,能挡掉80%的边界条件崩溃。千万别嫌麻烦跳过——我自己的血泪经验是,课设演示现场最怕的就是“选完起点终点,程序闪退”,那个场面会让前面所有的代码质量瞬间归零。多做这一步自测,省的是演示现场社死。
最后说一个个人习惯:我在实现这个课设时,先写GraphModel和Dijkstra算法(纯命令行里喂数据验证),再写QT界面,最后才做界面和数据结构的通信。这个顺序让我把“数据结构本身”和“QT展示层”分开测试,排查问题时不至于两个层面互相干扰。希望帮到你——数据结构课设不是比谁的界面花哨,而是比谁能把“数据怎么存、算法怎么走、坑怎么避”讲得干净利落,照着这个路线做完,你答辩时心里是有底气的。
本文还有配套的精品资源,点击获取