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

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


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

相关推荐
love_muming10 小时前
二叉树操作全解析:从递归到层序遍历
java·数据结构·算法·二叉树
.道阻且长.19 小时前
2.LeetCode算法习题讲解--双指针--复写零
算法·leetcode·职场和发展
To_OC20 小时前
LC 438 找到所有字母异位词:暴力超时后,我靠滑动窗口一招搞定
javascript·算法·leetcode
Forever Nore1 天前
学完C语言力扣第一题做不来正常吗
数据结构·算法
hansang_IR1 天前
【题解】LC:倍增 / 区间并查集(Range Parallel Unionfind)
c++·算法·并查集
Tisfy1 天前
LeetCode 3731.找出缺失的元素:哈希 / 排序
算法·leetcode·哈希算法·排序·哈希表
lucas_AI1 天前
Q-CueGraph:你的多模态大模型会 zoom,但真的知道该看哪儿吗?
人工智能·算法
kaixin_啊啊1 天前
test_机器学习算法学习
学习·算法·机器学习
liulilittle1 天前
MOE路由:路由(logits: top-k/8)
c++·人工智能·算法·机器学习·llm