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

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


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

相关推荐
小白羊丨1 小时前
如何诊断 Prompt 模板导致的效果下降?
人工智能·算法·prompt
OPEN-F2 小时前
C++11/14新特性精讲:移动语义与智能指针实战
开发语言·c++·算法
lisin-lee-cooper2 小时前
【leetcode658】有序数组找出k个最接近x的数
java·数据结构·算法
sunburn-2 小时前
Java堆(Heap)详解与实战教学
java·开发语言·数据结构·ide·算法
智碳能碳管理平台3 小时前
工业能耗台账标准化:能碳管理系统的数据口径怎么设计
算法·能碳管理系统·智碳能碳管理平台·企业能碳管理系统·碳排放核算软件·绿色工厂申报saas·能碳管理平台
带多刺的玫瑰3 小时前
Leecode#15刷题之三数之和
算法·leetcode·职场和发展
哭泣方源炼蛊4 小时前
并查集进阶 P1(带权并查集,并查集分类)
数据结构·c++·算法·二进制·带权并查集
牧羊人.3334 小时前
动手学深度学习 02 | 手写数字识别
图像处理·人工智能·深度学习·算法
武帝为此4 小时前
【InnoDB存储引擎介绍】
数据库·算法
shehuiyuelaiyuehao5 小时前
算法32,连续数组,前缀和+哈希表
算法·leetcode·职场和发展