一篇文章看到最短路径——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 让你"过题目"。

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


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

相关推荐
科学实验家11 小时前
二叉树的几道题
算法
abcy07121311 小时前
flink state实例
大数据·算法·flink
qizayaoshuap12 小时前
# [特殊字符] 密码生成器 — 鸿蒙ArkTS安全算法与密码强度评估系统
java·算法·安全·华为·harmonyos
良木林12 小时前
滑动窗口 - LeetCode hot 100
javascript·算法·leetcode·双指针·滑动窗口
2401_8414956412 小时前
【数据结构】B*树
数据结构·c++·b树·算法·删除·插入·三分分裂
QXWZ_IA13 小时前
**电力登高作业高空失保实时监管:千寻智能安全带高挂低用监测方案**
人工智能·科技·算法·能源·智能硬件
爱刷碗的苏泓舒13 小时前
FFRT:固定失败率比率检验、宽巷模糊度固定及 Ratio Test
算法·相位偏差·模糊度固定·ppp-ar·ffrt·ratio test·wlnl
小园子的小菜14 小时前
深入理解 JVM 垃圾回收:从对象判定、回收算法到经典收集器全解析
jvm·算法
2601_9545267514 小时前
【硬核长文】从卡门涡街物理方程到边缘网关温压补偿算法:工业蒸汽测控实战,深度解密靠谱的涡街流量计厂家有哪些
算法