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

从邻接表到链式前向星:手把手教你用C++实现Dijkstra最短路径算法(附完整代码)

从邻接表到链式前向星:深入解析Dijkstra算法的C++实现

在算法竞赛中,图论问题占据了相当大的比重,而最短路径算法则是图论中最基础也最常用的算法之一。Dijkstra算法作为解决单源最短路径问题的经典算法,其实现方式多种多样,不同的数据结构选择会直接影响算法的效率和适用场景。本文将深入探讨两种常见的邻接表实现方式——vector数组和链式前向星,并详细分析它们在Dijkstra算法中的应用。

1. 邻接表与链式前向星:数据结构的选择

1.1 邻接表的基本概念

邻接表是图的一种链式存储结构,它通过为每个顶点建立一个单链表来存储该顶点的所有邻接边。相比于邻接矩阵,邻接表在稀疏图中能显著节省存储空间。

邻接表的核心优势

  • 空间复杂度为O(V+E),特别适合边数远小于顶点数平方的稀疏图
  • 可以快速访问某个顶点的所有邻接边
  • 动态添加边非常方便

1.2 vector数组实现邻接表

使用C++标准库中的vector来实现邻接表是最直观的方式。每个顶点对应一个vector,存储该顶点的所有出边。

struct Edge { int v; // 目标顶点 int w; // 边权重 Edge(int _v, int _w) : v(_v), w(_w) {} }; vector<vector<Edge>> adj(n + 1); // 邻接表表示

vector实现的优缺点对比

特性vector实现链式前向星
代码可读性
内存连续性一般
动态扩展方便需要预分配
缓存友好较好一般
内存占用较高较低

1.3 链式前向星的结构解析

链式前向星是一种静态链表形式的邻接表实现,它通过数组模拟链表,具有更好的内存控制能力。

struct Edge { int to; // 边的终点 int weight; // 边的权重 int next; // 下一条边的索引 }; Edge edges[MAX_EDGES]; // 边存储池 int head[MAX_NODES]; // 每个顶点的第一条边 int edge_count = 0; // 当前边数 void addEdge(int u, int v, int w) { edges[edge_count].to = v; edges[edge_count].weight = w; edges[edge_count].next = head[u]; head[u] = edge_count++; }

链式前向星的核心思想是通过head数组记录每个顶点的第一条边,然后通过next指针形成链表。这种实现方式在内存使用上更加紧凑,特别适合对内存要求严格的场景。

2. Dijkstra算法的核心实现

2.1 算法基本框架

Dijkstra算法的核心思想是贪心策略,通过不断扩展当前已知的最短路径来逐步求解所有顶点的最短距离。

算法基本步骤

  1. 初始化:设置起点距离为0,其他顶点距离为无穷大
  2. 选择当前距离最小的未处理顶点u
  3. 对u的所有邻接边进行松弛操作
  4. 标记u为已处理
  5. 重复步骤2-4,直到所有顶点都被处理

2.2 朴素Dijkstra实现

使用邻接表的朴素Dijkstra实现时间复杂度为O(V²),适合稠密图。

void dijkstra(int start) { vector<int> dist(n + 1, INF); vector<bool> visited(n + 1, false); dist[start] = 0; for (int i = 1; i <= n; ++i) { int u = -1; // 找到未访问的距离最小的顶点 for (int j = 1; j <= n; ++j) { if (!visited[j] && (u == -1 || dist[j] < dist[u])) { u = j; } } if (u == -1) break; visited[u] = true; // 遍历u的所有邻接边 for (const Edge& e : adj[u]) { if (dist[e.v] > dist[u] + e.w) { dist[e.v] = dist[u] + e.w; } } } }

2.3 堆优化的Dijkstra实现

对于稀疏图,使用优先队列优化可以将时间复杂度降为O(ElogV)。

void dijkstra_heap(int start) { vector<int> dist(n + 1, INF); priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq; dist[start] = 0; pq.push({0, start}); while (!pq.empty()) { auto [d, u] = pq.top(); pq.pop(); if (d > dist[u]) continue; // 已经找到更优解 for (const Edge& e : adj[u]) { if (dist[e.v] > dist[u] + e.w) { dist[e.v] = dist[u] + e.w; pq.push({dist[e.v], e.v}); } } } }

注意:堆优化版本中,同一个顶点可能被多次加入优先队列,因此需要检查弹出的距离是否是最新的。

3. 两种邻接表实现的性能对比

3.1 内存占用分析

在内存使用方面,链式前向星通常比vector实现更加紧凑:

  • vector实现:每个vector会额外存储容量、大小等信息,且动态扩容可能导致内存碎片
  • 链式前向星:仅使用固定大小的数组,没有额外开销

内存占用对比表

操作vector实现链式前向星
基础存储O(V+E)O(V+E)
额外开销较高
动态扩展可能浪费空间固定大小
适合场景边数不确定边数已知

3.2 访问效率对比

访问效率受多种因素影响,包括缓存命中率、内存局部性等。

// vector实现的边遍历 for (const Edge& e : adj[u]) { // 处理边 } // 链式前向星的边遍历 for (int i = head[u]; i != -1; i = edges[i].next) { Edge& e = edges[i]; // 处理边 }

性能影响因素

  • vector实现具有更好的内存局部性,可能获得更好的缓存命中率
  • 链式前向星的访问模式更加随机,缓存效率可能较低
  • 现代CPU的预取机制可能对两种实现产生不同影响

3.3 实际测试数据

在实际测试中(使用10000个顶点,20000条边的随机图):

指标vector实现链式前向星
内存使用约3.2MB约2.1MB
运行时间48ms52ms
代码行数较少较多

4. 实战应用与常见问题

4.1 算法竞赛中的选择策略

在算法竞赛中,选择哪种实现方式需要考虑以下因素:

  1. 问题规模:对于极大图(顶点数>1e5),链式前向星的内存优势更明显
  2. 编码时间:vector实现编写更快,适合时间紧张的比赛
  3. 个人习惯:选择自己更熟悉、调试更方便的实现

推荐选择

  • 初学者:优先使用vector实现,更易理解和调试
  • 高级选手:掌握链式前向星,应对极端内存限制的情况

4.2 常见错误与调试技巧

在实现Dijkstra算法时,容易遇到以下问题:

  1. 无穷大值设置不当

    • 使用0x3f3f3f3f作为INF是一个常见选择
    • 确保INF足够大但不会导致加法溢出
  2. 优先队列的排序问题

    // 错误的比较方式会导致优先队列行为异常 struct Node { int u, d; bool operator<(const Node& other) const { return d > other.d; // 正确应该是小根堆 } };
  3. 重复松弛的检查

    • 堆优化版本中,弹出节点时需要检查距离是否最新
    • 避免不必要的重复计算

4.3 完整代码示例

以下是使用链式前向星实现的堆优化Dijkstra完整代码:

#include <iostream> #include <vector> #include <queue> #include <climits> using namespace std; const int MAX_NODES = 1e5 + 10; const int MAX_EDGES = 2e5 + 10; const int INF = 0x3f3f3f3f; struct Edge { int to; int weight; int next; }; Edge edges[MAX_EDGES]; int head[MAX_NODES]; int edge_count = 0; int n, m; void addEdge(int u, int v, int w) { edges[edge_count] = {v, w, head[u]}; head[u] = edge_count++; } void dijkstra(int start, vector<int>& dist) { dist.assign(n + 1, INF); vector<bool> visited(n + 1, false); priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq; dist[start] = 0; pq.push({0, start}); while (!pq.empty()) { auto [d, u] = pq.top(); pq.pop(); if (visited[u]) continue; visited[u] = true; for (int i = head[u]; i != -1; i = edges[i].next) { int v = edges[i].to; int w = edges[i].weight; if (dist[v] > dist[u] + w) { dist[v] = dist[u] + w; pq.push({dist[v], v}); } } } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> n >> m; fill(head, head + n + 1, -1); for (int i = 0; i < m; ++i) { int u, v, w; cin >> u >> v >> w; addEdge(u, v, w); addEdge(v, u, w); // 无向图 } vector<int> dist; dijkstra(1, dist); cout << dist[n] << endl; return 0; }

在实际编码中,我发现链式前向星的初始化容易被忽视,特别是head数组的初始化必须设置为-1,否则边遍历可能会出错。另外,无向图的边需要添加两次,这也是常见的错误点。

http://www.jsqmd.com/news/978337/

相关文章:

  • 2026温州发光字标牌服务商TOP5排行:温州科室标牌、温州科室牌、温州精神堡垒、温州警示牌、温州门牌、温州不锈钢雕塑选择指南 - 优质品牌商家
  • 免费备份QQ空间历史说说的终极指南:GetQzonehistory完整使用教程
  • 【无人机】基于GWO算法、MP-GWO灰狼算法、灰狼-布谷鸟优化算法、CS-GWO多种群灰狼优化算法的无人机路径规划(Matlab代码实现)
  • 避坑指南:VS Code verilog-format插件配置常见报错解决(附Windows/Mac配置差异)
  • 2026年想找口碑好的机器人外壳加工服务商?这些方法实用又靠谱
  • 用ESP32的GPIO唤醒功能做个低功耗遥控器:Light-sleep模式与gpio_wakeup_enable实战
  • Audacity如何解决专业音频处理难题:开源音频编辑的完整实战指南
  • Vivado调试之痛:遇到‘debug hub core not detected’?别慌,这份Ibert核识别失败排查清单请收好
  • 别再死记硬背了!奇数分频(3/5/7分频)的Verilog通用模板与设计思想详解
  • 从零到一:STM32 Modbus通信学习笔记——理论基础
  • 云南土工格栅拉力越大越好吗?
  • 准确率狂飙34%!谷歌全新Agentic RAG来了:揪出缺失盲点,AI不搜出真相绝不停手
  • 2026年防爆门实测评测:四川入户门、四川别墅入户门、四川加厚防盗门、四川单开门、四川子母门、四川安全门、四川家用防盗门选择指南 - 优质品牌商家
  • 将RK3588s/LubanCat4开发板IMX415摄像头官方4k30fps驱动修改为4K60fps完全指北
  • 2026郑州自流平砂浆技术选型指南:郑州聚合物砂浆/郑州聚合物砂浆/郑州金刚灰砂浆/郑州金刚灰砂浆/郑州防水抗裂砂浆/选择指南 - 优质品牌商家
  • 第一次LLM驱动mcp根据api key检索法律法规和案例等
  • 2016年6月重庆配眼镜最新排行指南:5家连锁品牌实测对比 - 奔跑123
  • STM32 Modbus通信实战:从硬件到软件的完整指南
  • 2026年揭秘:玻璃钢雕塑褪色背后的真实原因
  • 手把手教你用Simulink搭建异步电机矢量控制模型(附完整PI参数调试心得)
  • 哈氏合金无缝管哪个品牌好? - 工业设备
  • Chaldea终极指南:如何免费实现FGO素材规划与战斗模拟一体化管理
  • 别再只用点击数据了!用阿里ESMM模型搞定转化率预估的样本偏差与稀疏难题
  • 别再死磕LeetCode了!牛客网ACM模式实战指南(附Java输入输出模板)
  • 手把手教你用Simulink搭建异步电机矢量控制模型(附PI参数调试心得)
  • 人工智能伦理与职业操守(理论篇)
  • 用STM32F103驱动TPC116S8 DAC芯片:一个完整工程代码的解析与移植指南
  • 能提供清洗维保服务的不锈钢水箱多少钱 - 工业设备
  • OpenDroneMap终极指南:免费无人机照片转3D模型从入门到精通
  • Panda3D:开源 3D 游戏引擎,Python 与 C++ 双语言支持