当前位置: 首页 > news >正文

用C++手把手实现Dijkstra算法:从邻接矩阵到最短路径的完整代码解析

用C++手把手实现Dijkstra算法:从邻接矩阵到最短路径的完整代码解析

当你第一次在地图应用中输入起点和终点,瞬间计算出最优路线时,背后很可能就运行着Dijkstra算法。这个诞生于1956年的经典算法,至今仍是解决单源最短路径问题的黄金标准。本文将带你从零开始,用C++完整实现一个带邻接矩阵的Dijkstra算法,不仅理解其原理,更能亲手写出工业级可运行的代码。

1. 邻接矩阵:图的数字化表达

邻接矩阵是图论中最直观的存储方式之一。假设我们有一个包含4个城市(A、B、C、D)的交通网,用矩阵表示如下:

ABCD
A037
B302
C201
D710

在C++中,我们可以用二维数组来实现这个结构。先定义图的基本框架:

#define MAX_VERTEX 100 // 最大顶点数 #define INF 0x3f3f3f3f // 表示无穷大 struct Graph { char vertex[MAX_VERTEX]; // 顶点集合 int matrix[MAX_VERTEX][MAX_VERTEX]; // 邻接矩阵 int vertexNum, edgeNum; // 当前顶点数和边数 };

初始化矩阵时,需要特别注意对角线和不可达边的处理:

void initGraph(Graph &G) { for(int i=0; i<MAX_VERTEX; ++i) { for(int j=0; j<MAX_VERTEX; ++j) { G.matrix[i][j] = (i == j) ? 0 : INF; } } }

2. Dijkstra算法的核心实现

Dijkstra算法的精妙之处在于它如何逐步"探索"图中的节点。想象你是一位城市规划师,正在逐步绘制从市中心到各区域的最短路线图。

2.1 关键数据结构

算法需要维护三个核心数组:

  1. dist[]:记录源点到各顶点的当前最短距离
  2. visited[]:标记顶点是否已确定最短路径
  3. path[]:记录路径上的前驱节点
void dijkstra(const Graph &G, int src) { int dist[MAX_VERTEX]; bool visited[MAX_VERTEX] = {false}; int path[MAX_VERTEX]; // 初始化 for(int i=0; i<G.vertexNum; ++i) { dist[i] = G.matrix[src][i]; path[i] = (dist[i] != INF) ? src : -1; } visited[src] = true; dist[src] = 0; // 主循环 for(int i=1; i<G.vertexNum; ++i) { int minDist = INF; int u = src; // 找出未访问节点中的最近节点 for(int j=0; j<G.vertexNum; ++j) { if(!visited[j] && dist[j] < minDist) { minDist = dist[j]; u = j; } } visited[u] = true; // 松弛操作 for(int v=0; v<G.vertexNum; ++v) { if(!visited[v] && G.matrix[u][v] != INF) { if(dist[u] + G.matrix[u][v] < dist[v]) { dist[v] = dist[u] + G.matrix[u][v]; path[v] = u; } } } } }

2.2 算法复杂度分析

Dijkstra算法的时间复杂度主要取决于如何实现最小距离节点的查找:

实现方式时间复杂度适用场景
数组遍历O(V²)稠密图
最小堆O(E + VlogV)稀疏图
斐波那契堆O(E + VlogV)理论最优

我们的实现采用了最简单的数组遍历方式,适合教学目的。在实际工程中,根据图的特点可以选择更高效的实现。

3. 路径重建与输出

计算出最短距离后,我们还需要展示具体的路径。这需要用到之前记录的path数组:

void printPath(const Graph &G, const int path[], int dest) { if(path[dest] == -1) { cout << G.vertex[dest]; return; } printPath(G, path, path[dest]); cout << " -> " << G.vertex[dest]; }

这个递归函数会从终点回溯到起点,然后正向输出路径。例如对于路径A→B→C→D,实际存储是:

path[D] = C path[C] = B path[B] = A

4. 完整可运行示例

让我们用一个实际的交通网络测试我们的实现。假设有以下城市和路线:

城市:A B C D E 路线: A B 5 A C 2 B D 4 C B 1 C D 6 D E 3

完整的主函数实现如下:

int main() { Graph G; initGraph(G); cout << "输入顶点数和边数:"; cin >> G.vertexNum >> G.edgeNum; cout << "输入顶点名称:"; for(int i=0; i<G.vertexNum; ++i) { cin >> G.vertex[i]; } cout << "输入边(起点 终点 权重):" << endl; for(int i=0; i<G.edgeNum; ++i) { char u, v; int w; cin >> u >> v >> w; int ui = findVertexIndex(G, u); int vi = findVertexIndex(G, v); G.matrix[ui][vi] = w; } cout << "输入起点:"; char start; cin >> start; int src = findVertexIndex(G, start); dijkstra(G, src); cout << "最短路径结果:" << endl; for(int i=0; i<G.vertexNum; ++i) { if(i != src) { cout << start << "到" << G.vertex[i] << "的最短距离:" << dist[i]; cout << ",路径:"; printPath(G, path, i); cout << endl; } } return 0; }

运行这个程序,输入上述测试数据,你将得到类似如下的输出:

A到B的最短距离:3,路径:A -> C -> B A到C的最短距离:2,路径:A -> C A到D的最短距离:7,路径:A -> C -> B -> D A到E的最短距离:10,路径:A -> C -> B -> D -> E

5. 常见问题与优化技巧

在实际编码过程中,你可能会遇到以下典型问题:

  1. 负权边处理:Dijkstra算法不能处理含负权边的图。如果图中存在负权边,应该改用Bellman-Ford算法。

  2. 堆优化实现:当图的规模较大时,可以使用优先队列来优化查找最小距离节点的过程:

priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pq; pq.push({0, src}); while(!pq.empty()) { auto [dist_u, u] = pq.top(); pq.pop(); if(visited[u]) continue; visited[u] = true; for(int v=0; v<G.vertexNum; ++v) { if(G.matrix[u][v] != INF) { if(dist[u] + G.matrix[u][v] < dist[v]) { dist[v] = dist[u] + G.matrix[u][v]; path[v] = u; pq.push({dist[v], v}); } } } }
  1. 路径记录优化:当只需要计算特定两点间的最短路径时,可以在确定目标节点的最短路径后提前终止算法。

  2. 内存优化:对于超大图,邻接矩阵可能占用过多内存,此时可考虑改用邻接表存储结构。

http://www.cnnetsun.cn/news/1647591.html

相关文章:

  • 告别手动改IP!用ddns-go在Ubuntu上自动同步IPv6地址到阿里云DNS
  • PC端微信小程序接口抓包实战:2024年绕过反代理新思路
  • 【learn-claude-code】S04Subagent - 子 Agent:每个子任务需要干净的上下文
  • ccmusic-database/music_genre开源可部署:支持国产昇腾/寒武纪芯片适配路线
  • Wan2.2-I2V-A14B效果展示:动态运镜+光影变化的高质量视频样例
  • PlugY生存工具包:暗黑破坏神2单机玩家的终极增强方案
  • 后端实战实战案例
  • KMS激活技术的自动化解决方案:KMS_VL_ALL_AIO的实现原理与企业应用
  • 视频修复神器Untrunc:从损坏到完整的10倍速高效恢复实战
  • 企业微信扫码登录全流程解析(附完整代码实现)
  • Obsidian插件翻译终极指南:5分钟让所有插件说你的语言
  • 【VRChat 改模】从零到一:手把手配置 VCC、SDK 与 Unity 全流程
  • 实战应用:基于快马平台构建企业级9-1免费安装预约系统
  • 5个维度彻底掌握GitHub中文插件:从入门到精通的界面本地化方案
  • 突破网络性能瓶颈:iperf3 Windows版全方位测试指南
  • 智能看图说话!Llama-3.2V-11B-cot应用案例:图片分析、逻辑推理实战
  • AD5522与STM32的完美协作:从SPI通信到Python上位机开发全攻略
  • 轻量级LoRA文生图模型应用:雯雯的后宫-Z-Image在健身博主内容生产中的提效实践
  • 如何用CyberChef解决90%的数据处理难题:从入门到精通指南
  • 开源工具Cursor Free VIP:突破AI编程限制的高效使用指南
  • 5大核心优势解析:为什么Blueman是Linux桌面最专业的蓝牙管理工具
  • 小白友好:用PyTorch 2.8镜像微调BERT模型,零配置体验完整训练流程
  • 先进人力资源系统,如何为企业人才管理赋能?
  • 【愚公系列】《剪映+DeepSeek+即梦:短视频制作》040-合成:开启视觉冲击魔法(用剪映专业版合成视频)
  • 突破性智能音乐解决方案:XiaoMusic开源项目实战深度解析
  • Python 增强提案:明确 WebAssembly 标准,重塑 Python 应用交付格局
  • 终极指南:如何使用applera1n工具在iOS 15-16.6上绕过激活锁
  • GitHub OCaml项目:C++后端突破与代码编译新变革
  • 干农活总腰疼?农民朋友别再硬扛腰突
  • 免费开源的质谱分析革新工具:从数据到发现的完整路径