一篇文章看到最短路径——Dijkstra算法

从朴素到优雅:Dijkstra 算法的两种实现详解

本文基于一道经典最短路题目,对比 朴素 Dijkstra堆优化 Dijkstra 的实现方式、易错点与适用场景,适合算法初学者系统理解。


一、问题背景

给定一个含 nnn 个节点、mmm 条边的无向图 ,求从起点 xxx 到终点 yyy 的最短距离。

数据范围:

  • n≤10n \leq 10n≤10
  • m≤n(n−1)2m \leq \frac{n(n-1)}{2}m≤2n(n−1)
  • 边权 1≤l≤201 \leq l \leq 201≤l≤20

👉 这是一个典型单源最短路径问题,非常适合用来对比两种 Dijkstra 写法。


二、朴素 Dijkstra:小数据的最佳选择

1️⃣ 算法思想

朴素 Dijkstra 的核心是贪心 + 暴力扫描

  1. 每次从「未确定最短路径的点」中选出距离起点最近的点
  2. 用该点去松弛其他所有点
  3. 重复 nnn 次

时间复杂度:

O(n2) O(n^2) O(n2)

2️⃣ 代码实现(修正版)

cpp 复制代码
#include <cstring>
#include <iostream>
using namespace std;

const int N = 25;
const int INF = 0x3f3f3f3f;

int g[N][N];     // 邻接矩阵
int dist[N];     // 最短距离
bool vis[N];     // 是否已确定最短路
int n, m, x, y;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    while (cin >> n >> m) {
        // 初始化
        memset(g, 0x3f, sizeof g);
        memset(dist, 0x3f, sizeof dist);
        memset(vis, 0, sizeof vis);

        for (int i = 1; i <= n; i++) g[i][i] = 0;

        for (int i = 1; i <= m; i++) {
            int a, b, c;
            cin >> a >> b >> c;
            g[a][b] = g[b][a] = min(g[a][b], c);
        }

        cin >> x >> y;
        dist[x] = 0;

        // Dijkstra
        for (int i = 1; i <= n; i++) {
            int u = -1, minn = INF;
            for (int j = 1; j <= n; j++) {
                if (!vis[j] && dist[j] < minn) {
                    minn = dist[j];
                    u = j;
                }
            }

            if (u == -1) break;   // 图不连通
            vis[u] = true;

            for (int v = 1; v <= n; v++) {
                if (dist[v] > dist[u] + g[u][v]) {
                    dist[v] = dist[u] + g[u][v];
                }
            }
        }

        if (dist[y] == INF)
            cout << "No path\n";
        else
            cout << dist[y] << "\n";
    }
    return 0;
}

3️⃣ 常见错误(初学者必踩)

错误 后果
邻接矩阵未初始化 旧数据污染,WA
第一次循环未保证选到起点 未定义行为
g[u][v] != 0 判断边 逻辑不严谨
忽略多组输入 TLE / WA

朴素版适合:n ≤ 500


三、堆优化 Dijkstra:大数据的标准解法

1️⃣ 为什么需要堆优化?

当 nnn 达到 10510^5105 时:

  • 朴素版:O(n2)O(n^2)O(n2) → 超时
  • 堆优化:O((n+m)log⁡n)O((n+m)\log n)O((n+m)logn) → 可行

核心优化点:

用优先队列快速找到"当前距离最小的点"


2️⃣ 算法思想

  1. 使用小根堆 维护 (距离, 节点)
  2. 每次取出堆顶作为当前最短点
  3. 用该点松弛相邻节点,并将更新后的结果重新入堆

3️⃣ 代码实现(链式前向星 + 优先队列)

cpp 复制代码
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;

typedef pair<int, int> pii;
const int N = 510;
const int M = N * N;
const int INF = 0x3f3f3f3f;

int h[N], e[M], ne[M], w[M], idx;
int dist[N];
bool vis[N];
int n, m, x, y;

priority_queue<pii, vector<pii>, greater<pii>> heap;

void add(int a, int b, int c) {
    e[idx] = b;
    ne[idx] = h[a];
    w[idx] = c;
    h[a] = idx++;
}

int dijkstra(int s, int t) {
    memset(dist, 0x3f, sizeof dist);
    memset(vis, 0, sizeof vis);
    dist[s] = 0;
    heap.push({0, s});

    while (!heap.empty()) {
        auto t = heap.top();
        heap.pop();
        int u = t.second;

        if (vis[u]) continue;   // 已确定最短路
        vis[u] = true;

        for (int i = h[u]; i != -1; i = ne[i]) {
            int v = e[i];
            if (dist[v] > dist[u] + w[i]) {
                dist[v] = dist[u] + w[i];
                heap.push({dist[v], v});
            }
        }
    }
    return dist[t];
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    while (cin >> n >> m) {
        memset(h, -1, sizeof h);
        idx = 0;

        for (int i = 0; i < m; i++) {
            int a, b, c;
            cin >> a >> b >> c;
            add(a, b, c);
            add(b, a, c);
        }

        cin >> x >> y;
        int ans = dijkstra(x, y);

        if (ans == INF)
            cout << "No path\n";
        else
            cout << ans << "\n";
    }
    return 0;
}

4️⃣ 为什么同一个点会多次入堆?

  • 每次松弛都可能产生一个"更优距离"
  • 堆中旧版本会被 vis 数组过滤
  • 这是正常且必要的设计

四、两种写法对比总结

对比项 朴素 Dijkstra 堆优化 Dijkstra
存图方式 邻接矩阵 邻接表 / 链式前向星
选最小点 暴力扫描 优先队列
时间复杂度 O(n2)O(n^2)O(n2) O((n+m)log⁡n)O((n+m)\log n)O((n+m)logn)
适用场景 n≤500n \leq 500n≤500 n≥105n \geq 10^5n≥105
代码难度 ⭐⭐⭐
教学价值 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐

五、学习建议(写给初学者)

不要一上来就写堆优化

  • 先写朴素版,理解贪心本质
  • 再写堆优化,理解"为什么需要堆"

算法选型比算法本身更重要

  • 小数据:简单算法更稳定
  • 大数据:复杂算法才有效

六、一句话总结

朴素 Dijkstra 让你"懂原理",堆优化 Dijkstra 让你"过题目"。

两者缺一不可,是每一位算法学习者必经的成长路径。


如果你喜欢这篇博客,欢迎点赞、收藏、关注 😊

相关推荐
倒头就睡的小比特6 小时前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!6 小时前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
猎头南楼8 小时前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
旖旎夜光9 小时前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
wzdark9 小时前
大规模并行计算中的负载均衡算法研究4
算法
Because_of_Her19 小时前
并查集-听课笔记
笔记·算法·并查集
码流子9 小时前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构
another heaven9 小时前
【算法/C++ MD5算法能否逆解码?原理、C++实现与同类哈希算法对比】
c++·算法·哈希算法
wzdark9 小时前
从算法设计模式看编程思维的抽象能力4
算法
2601_9622186110 小时前
万象生鲜系统称重自动多退少补算法解决生鲜非标品痛点
大数据·数据库·人工智能·python·算法