学习dijkstra算法(c++)

今天我们一起来学习图论最短路径算法之一的Dijkstra算法!

在图论算法中,最短路径是最核心、最高频的考点之一,而 Dijkstra(迪杰斯特拉)算法 是求解正权图单源最短路径的最优算法。无论是算法竞赛、编程刷题,还是实际项目中的路径规划、导航测距、网络拓扑计算,Dijkstra 算法都有着不可替代的作用。

本文将从零基础原理讲解入手,手把手带大家掌握 C++ 实现的两种版本:适配稠密图的朴素版 Dijkstra、适配大数据稀疏图的堆优化版 Dijkstra。全文搭配完整可运行代码、详细注释、测试样例、易错点解析,内容详实完整,适合新手学习、收藏复盘,也可直接作为博客干货内容留存。

一、算法基础认知与适用场景

1. 核心定义

单源最短路径:给定一张带权图和一个起始节点(源点),求解源点到图中所有其他节点的最短路径长度

Dijkstra 算法是基于贪心思想 的最短路径算法,核心特性:仅适用于边权非负的图(无负权边)

2. 优缺点与适用场景

  • 优点:效率高、逻辑清晰、稳定性强,正权图下时间复杂度远优于 Bellman-Ford、SPFA 算法。

  • 缺点:无法处理负权边、负权环,一旦存在负权边,贪心逻辑失效,计算结果出错。

  • 场景区分:朴素版适配节点数较少的稠密图,堆优化版适配大规模稀疏图,是工业级和竞赛级的通用模板。

3. 核心前置概念

松弛操作(算法灵魂):假设已知源点到节点 u 的最短距离为 distu,存在一条边 u→v,边权为 w。若通过 u 中转到达 v 的距离,比当前记录的源点到 v 的距离更短,则更新 v 的最短距离。

公式表达:

简单理解:发现更短的路径,就更新最短距离,所有 Dijkstra 算法的核心逻辑都围绕松弛操作展开。

二、通用变量约定(全文统一)

  • n:图的节点总数

  • m:图的边总数

  • s:起始源点

  • dist[]:距离数组,dist[i] 表示源点到 i 号节点的最短距离

  • vis[]:标记数组,记录节点的最短路径是否已确定

  • INF:无穷大,本文统一使用 0x3f3f3f3f,数值稳定、不易溢出,是 C++ 图论通用无穷值

三、朴素版 Dijkstra 算法(邻接矩阵 · 新手入门)

1. 原理与复杂度

朴素版采用邻接矩阵存图,核心逻辑:每次遍历所有未确定最短路径的节点,选出距离源点最近的节点,固定其最短路径,再通过该节点松弛所有相邻节点。

时间复杂度:O(n²),适合节点数 ≤ 1000 的稠密图,逻辑直观,是新手理解 Dijkstra 算法的最佳版本。

2. 完整可运行代码(超详细注释)

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

// 最大节点数
const int N = 505;
// 无穷大常量
const int INF = 0x3f3f3f3f;

int g[N][N];    // 邻接矩阵存图:g[u][v] 表示 u到v 的边权
int dist[N];    // 存储源点到各点的最短距离
bool vis[N];    // 标记节点是否已确定最短路径
int n, m, s;    // 节点数、边数、源点

// 朴素Dijkstra核心函数
void Dijkstra()
{
    // 初始化所有距离为无穷大
    memset(dist, 0x3f, sizeof(dist));
    // 源点到自身距离为0
    dist[s] = 0;

    // 遍历n轮,每轮确定一个节点的最短路径
    for (int i = 1; i <= n; i++)
    {
        // 步骤1:找到未访问、距离最小的节点u
        int u = -1;
        for (int j = 1; j <= n; j++)
        {
            if (!vis[j] && (u == -1 || dist[j] < dist[u]))
            {
                u = j;
            }
        }

        // 标记该节点最短路径已确定
        vis[u] = true;

        // 步骤2:用节点u松弛所有相邻节点
        for (int v = 1; v <= n; v++)
        {
            // 两点之间存在有效边时,执行松弛操作
            if (g[u][v] != INF)
            {
                dist[v] = min(dist[v], dist[u] + g[u][v]);
            }
        }
    }
}

int main()
{
    // 初始化邻接矩阵为无穷大
    memset(g, 0x3f, sizeof(g));
    cin >> n >> m >> s;

    // 读入所有边
    for (int i = 1; i <= m; i++)
    {
        int u, v, w;
        cin >> u >> v >> w;
        // 处理重边:保留两点间最小边权
        g[u][v] = min(g[u][v], w);
    }

    // 执行最短路算法
    Dijkstra();

    // 输出源点到所有节点的最短距离
    for (int i = 1; i <= n; i++)
    {
        if (dist[i] == INF)
            cout << "INF ";
        else
            cout << dist[i] << " ";
    }
    return 0;
}

3. 测试样例与运行结果

输入样例

5 7 1 1 2 2 1 3 1 2 4 5 3 2 1 3 4 3 3 5 4 4 5 1

输出结果0 2 1 4 5

结果释义:源点1到1、2、3、4、5号节点的最短距离分别为 0、2、1、4、5。

四、堆优化 Dijkstra 算法(邻接表 · 竞赛/工程标配)

1. 优化原理

朴素算法的最大短板是:每次遍历全图寻找最短距离节点,造成大量无效遍历。针对这一问题,我们采用**邻接表存图+小根堆(优先队列)**优化。

通过优先队列自动排序,可直接取出当前距离最小的节点,将选点复杂度从 O(n) 降至 O(logn),整体时间复杂度优化为 O(m logn),可处理 1e5 级别的大规模数据,是实际开发和算法竞赛的通用模板。

2. 核心知识点

  • 邻接表 :用 vector<pair<int, int>> 存储边,仅记录有效边,大幅节省空间,适配稀疏图。

  • 小根堆 :默认优先队列是大根堆,通过 greater 重载为小根堆,实现最小距离优先出队。

  • 冗余节点处理:同一节点会多次入队,只需取出时判断是否已确定最短路,已确定则直接跳过即可。

3. 完整优化版代码

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

// 适配1e5大规模数据
const int N = 100010;
const int INF = 0x3f3f3f3f;

// 邻接表:adj[u] 存储所有 u 出发的边 {终点, 边权}
vector<pair<int, int>> adj[N];
int dist[N];
bool vis[N];
int n, m, s;

void Dijkstra_Heap()
{
    memset(dist, 0x3f, sizeof(dist));
    dist[s] = 0;

    // 小根堆:pair(距离, 节点编号),距离最小优先
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
    q.push({0, s});

    while (!q.empty())
    {
        // 取出当前距离最小的节点
        auto now = q.top();
        q.pop();
        int d = now.first;
        int u = now.second;

        // 跳过已确定最短路的冗余节点
        if (vis[u]) continue;
        vis[u] = true;

        // 遍历所有邻边,执行松弛操作
        for (auto edge : adj[u])
        {
            int v = edge.first;
            int w = edge.second;
            if (dist[v] > dist[u] + w)
            {
                dist[v] = dist[u] + w;
                q.push({dist[v], v});
            }
        }
    }
}

int main()
{
    // 关闭cin同步,大幅加速大数据输入
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> m >> s;
    for (int i = 1; i <= m; i++)
    {
        int u, v, w;
        cin >> u >> v >> w;
        // 有向图建边
        adj[u].push_back({v, w});
        // 无向图需额外添加:adj[v].push_back({u, w});
    }

    Dijkstra_Heap();

    // 批量输出结果
    for (int i = 1; i <= n; i++)
    {
        if (dist[i] == INF)
            cout << "INF ";
        else
            cout << dist[i] << " ";
    }
    return 0;
}

五、博客必备:高频易错点总结

这部分是新手最容易踩坑的内容,也是刷题、面试高频考点,建议重点记忆:

  1. 禁止处理负权边:Dijkstra 依赖贪心思想,一旦存在负权边,已确定的最短路径可能被后续更新,算法直接失效,负权图请使用 SPFA 算法。

  2. INF 取值禁忌 :不要使用 INT_MAX,会导致加法溢出报错,0x3f3f3f3f 是最安全的无穷值。

  3. 无向图建边规则 :无向边是双向边,需要两次 push_back 建边,否则路径缺失。

  4. 堆冗余不删除 :优先队列无需手动删除旧数据,判断 vis 标记跳过即可,这是最优写法。

  5. 大数据加速必备 :处理 1e4 以上数据时,必须添加 ios::sync_with_stdio(false);cin.tie(0);,避免输入超时。

六、学习总结

  1. 朴素版 Dijkstra:逻辑简单、适合入门理解算法原理,仅用于小数据稠密图,日常学习优先先掌握此版本,吃透贪心和松弛核心思想。

  2. 堆优化 Dijkstra:效率极高、适配大数据,是开发、刷题、面试的万能模板,建议直接背诵留存。

  3. 算法核心本质:贪心选最短节点 + 松弛更新路径,所有优化方式都是在优化"选节点"的效率,核心逻辑从未改变。

给个关注再走吧!!!!

相关推荐
暖阳华笺1 小时前
【高频考点】回溯(暴力搜索)
数据结构·c++·算法·回溯法
AI浩1 小时前
学习率调度分层式精讲:Warmup、Cosine、Linear Decay 与大模型训练节奏(分层式精讲)
学习
我命由我123451 小时前
Excel - Excel 覆盖模式与编辑模式
运维·学习·职场和发展·excel·求职招聘·职场发展·运维开发
H_老邪1 小时前
Docker 学习之路-Linux安装指定版本docker
学习·docker·容器
lightqjx1 小时前
【算法】数据结构_单调队列
数据结构·算法·单调队列
c++之路1 小时前
Linux 下 C++ 开发环境搭建
linux·运维·c++
「維他檸檬茶」1 小时前
大模型算法学习6.3
学习
小四季豆1 小时前
《数据结构与算法》-顺序表:算法落地的第一个线性结构
c语言·数据结构·算法
数智工坊1 小时前
周志华《Machine Learning》学习笔记--第五章--神经网络
人工智能·笔记·神经网络·学习·机器学习