迪杰斯特拉(Dijkstra)算法:单源最短路与赛场路线规划

引言

近期 南昌市中小学生信息学奥林匹克竞赛 报名正在进行(比赛时间 2026 年 10 月 25 日,报名窗口 9 月 29 日---10 月 16 日),其提高组考察范围明确把「图论算法」列入重点。而在图论里,单源最短路是最基础、也最常考的模型之一:给定一张带权图和一个起点,求起点到所有点的最短距离。

今天我们用一道原创题「校园科技节赛场路线规划」带你吃透 迪杰斯特拉(Dijkstra)算法,并给出 C++ 与 Python 双语言实现、考点拆解、易错点与进阶方向。

一、题目 / 项目目标

【原创题】校园科技节赛场路线规划

校园科技节在建,共有 n 个场地(编号 1~n),校门在 1 号场地。任意两个场地之间可能有一条双向通道,走完需要 t 分钟(t>0)。作为志愿者调度员,你需要算出从校门(1 号)到每个场地的最短通行时间;特别地,到 k 号比赛主场馆的最短时间是多少?

  • 输入:
  • 第一行 n, m, k(场地数、通道数、目标场馆编号,2 ≤ n ≤ 10⁵,1 ≤ m ≤ 2×10⁵)
  • 接下来 m 行,每行 u, v, t(u、v 之间有一条耗时 t 分钟的双向通道,1 ≤ t ≤ 10⁴)
  • 输出:
  • 第一行输出 n 个整数,第 i 个为校门到 i 号场地的最短时间;若不可达输出 -1。
  • 第二行输出校门到 k 号场馆的最短时间(不可达输出 -1)。

样例输入

复制代码
5 6 5
1 2 2
1 3 4
2 3 1
2 4 7
3 4 3
4 5 1

样例输出

复制代码
0 2 3 6 7
7

样例解释: 1→2 最短 2;1→2→3 最短 2+1=3(比直连 1→3 的 4 更优);到 4 走 1→2→3→4 = 2+1+3 = 6;到 5 再经 4 加 1 = 7。所以第一行 0 2 3 6 7,第二行 7。

二、核心考点

  1. 单源最短路模型与松弛操作(relaxation) :用 dis[v] = min(dis[v], dis[u] + w) 不断缩短距离。
  2. Dijkstra 的贪心正确性 :每次从「未确定」集合中取出当前距离最小的点,在边权非负的前提下,它的距离就已经是最短,可以永久确定。
  3. 堆(优先队列)优化:把朴素算法 O(V²) 的「找最小点」降为 O((V+E) log V)。
  4. 邻接表存图:稀疏图必须用邻接表,否则空间与时间都会炸。
  5. 已确定集合(vis 标记):避免同一个点被重复扩展。
  6. 不可达判定 :最终 dis 仍为 INF 的点即不可达,输出 -1。
  7. 距离可能很大 :累加后超过 int 范围,应使用 long long 或大整数。

三、解法 / 拆解

思路

  • 用邻接表 g[u] 存 (v, w) 这种「邻居 + 边权」对。
  • dis[i] 初始全为 INF,dis[1] = 0。
  • 小根堆存放元组 (距离, 节点),每次取出距离最小的节点 u:
  • 若 u 已被确定(visu 为真)则跳过------堆里可能残留它更早、更大的旧距离副本;
  • 否则标记 u 已确定,遍历它的所有邻居 v,若 dis[u] + w < dis[v] 就松弛并把新距离压入堆。
  • 最后按格式输出 dis 数组,仍为 INF 的输出 -1 即可。

C++ 实现(堆优化)

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll INF = 1e18;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m, k;
    if (!(cin >> n >> m >> k)) return 0;
    vector<vector<pair<int, int>>> g(n + 1);
    for (int i = 0; i < m; i++) {
        int u, v, t;
        cin >> u >> v >> t;
        g[u].emplace_back(v, t);
        g[v].emplace_back(u, t);   // 双向通道,存两次
    }
    vector<ll> dis(n + 1, INF);
    vector<bool> vis(n + 1, false);
    dis[1] = 0;
    priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<>> pq;
    pq.emplace(0, 1);
    while (!pq.empty()) {
        auto [d, u] = pq.top(); pq.pop();
        if (vis[u]) continue;       // 旧副本,跳过
        vis[u] = true;
        for (auto [v, w] : g[u]) {
            if (dis[u] + w < dis[v]) {
                dis[v] = dis[u] + w;
                pq.emplace(dis[v], v);
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        cout << (dis[i] == INF ? -1 : dis[i]) << (i == n ? "" : " ");
    }
    cout << "\n" << (dis[k] == INF ? -1 : dis[k]) << "\n";
    return 0;
}

Python 实现(heapq)

python 复制代码
import sys
import heapq

def main():
    data = sys.stdin.read().split()
    if not data:
        return
    it = iter(data)
    n = int(next(it)); m = int(next(it)); k = int(next(it))
    g = [[] for _ in range(n + 1)]
    for _ in range(m):
        u = int(next(it)); v = int(next(it)); t = int(next(it))
        g[u].append((v, t))
        g[v].append((u, t))      # 双向通道,存两次
    INF = float('inf')
    dis = [INF] * (n + 1)
    vis = [False] * (n + 1)
    dis[1] = 0
    pq = [(0, 1)]
    while pq:
        d, u = heapq.heappop(pq)
        if vis[u]:
            continue
        vis[u] = True
        for v, w in g[u]:
            if dis[u] + w < dis[v]:
                dis[v] = dis[u] + w
                heapq.heappush(pq, (dis[v], v))
    out = []
    for i in range(1, n + 1):
        out.append("-1" if dis[i] == INF else str(dis[i]))
    print(" ".join(out))
    print(-1 if dis[k] == INF else dis[k])

if __name__ == "__main__":
    main()

四、时间与空间复杂度

  • 时间复杂度 :每个节点入堆一次、每条边松弛一次,每次堆操作 O(log V),总计 O((V + E) log V)。
  • 空间复杂度 :邻接表 O(V + E),距离数组 O(V),堆最坏 O(E),合计 O(V + E)。
  • 选型提示:朴素 Dijkstra(未优化)为 O(V²),只适合稠密图(E ≈ V²);本题 E 可达 2×10⁵、V 达 1e5,必须用堆优化版本。

五、易错点

  1. 负权边:Dijkstra 要求边权非负。一旦出现负权,贪心「取最小即确定」不再成立,应改用 Bellman-Ford / SPFA(注意 SPFA 在稠密图或特殊构造下可能退化为 O(VE))。
  2. 堆里的旧副本 :同一个节点可能被多次压入(距离不断变小),弹出时必须用 vis[u] 判断并跳过,否则会重复扩展、甚至死循环。
  3. INF 取值与溢出 :dis 累加可能超过 int,请用 long long 或 Python 大整数;INF 取 1e18 量级,避免 INF + w 上溢变成负数。
  4. 重边与自环:邻接表直接存即可,算法天然处理重边(取更优者);自环不影响结果。
  5. 起点初始化 :dis[起点] = 0 千万别漏,否则全部仍是 INF。
  6. 双向通道:无向图要把边加两次,漏掉一次会直接 WA。

六、进阶

  • 最短路计数 :松弛时若 dis[u] + w == dis[v] 则把方案数累加(取模),注意要在「确定」顺序下合并,防止重复计数。
  • 严格次短路 :维护 dis1[v](最短路)与 dis2[v](严格次短),松弛时同时更新两者。
  • 路径还原 :用 pre[v] 记录最短路上 v 的前驱,从终点沿 pre 回溯即得具体路线。
  • 多源最短路(多个校门):建一个超级源点 S,向每个起点连一条权 0 的边,跑一次 Dijkstra 即可统算。
  • 与其他算法对比:无权图用 BFS(O(V+E) 更快);全源最短路用 Floyd(O(V³),适合 V 很小);带负权用 Bellman-Ford / SPFA。
  • 练习推荐:洛谷 P4779(标准堆优化模板)、P1339、P1462(二分答案 + 最短路)等。

七、小结与互动

迪杰斯特拉是图论最短路的基石,核心就三件事:贪心取最小 + 堆优化降复杂度 + 松弛更新距离。把它和 BFS(无权)、Floyd(全源)放到一张对照表里记忆,考试遇到「路线规划」「最少耗时」「最小代价」类题目就能快速选型。

你还有哪些图论算法想让我拆解?欢迎在评论区留言,下一篇我们可以讲最短路计数 或严格次短路的实战题。


📚 免费少儿编程资料(夸克网盘领取)

以下资料来自夸克网盘分享,点击链接可直接保存;若需在 App 内打开,也可复制下方明文链接:

  1. 全国青少年信息素养大赛复赛集训题目Python&C++.docx
    https://pan.quark.cn/s/93995d3cb150
  2. 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
    https://pan.quark.cn/s/da97b5dbf75d
  3. Python背记手册.pdf
    https://pan.quark.cn/s/7568ae9ca92b
  4. Python课程
    https://pan.quark.cn/s/a94bf02d00c6
  5. 2024信息素养大赛图形化复赛集训题答案3-9
    https://pan.quark.cn/s/6ccab7ec3cbc
  6. 2025年03月份电子学会考级真题
    https://pan.quark.cn/s/4403c4228912
  7. 2025全国青少年信息素养大赛赛项说明
    https://pan.quark.cn/s/d9d0df4a9f29
  8. 青少儿信息素养大赛编程资料
    https://pan.quark.cn/s/4ab6bd83be8a

资料持续更新,关注获取最新分享。

相关推荐
不会就选b1 小时前
算法日常・每日刷题--<动态规划>19
算法·leetcode·职场和发展
计算机编程指导师1 小时前
【计算机毕设推荐】基于Hadoop+Spark的公共交通运营数据分析与可视化系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习
大数据·hadoop·python·计算机·数据分析·spark·交通
Wx-bishekaifayuan1 小时前
springboot汉中旅游服务平台41442-计算机课程设计、毕业设计
spring boot·后端·python·django·课程设计·express·旅游
WeiXin_DZbishe2 小时前
基于springboot城市公交实时运营与调度系统-计算机毕设 附源码【课程设计】66173
java·vue.js·spring boot·后端·python·php·课程设计
泡茶喝茶写代码2 小时前
A股量化数据工程:从 REST 接口到策略信号(第 18 篇):放量缩量异动扫描
java·python·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
WeiXin_DZbishe2 小时前
基于Springboot+AI的kuall音乐广场的设计与实现-计算机毕设 附源码【课程设计】12715
vue.js·人工智能·spring boot·后端·python·php·课程设计
沫璃染墨2 小时前
《Qt从零入门系列(十三):QSS样式表详解——从选择器与层叠规则到控件美化实战》
开发语言·css·c++·qt·ui·qt6.3·qss