导读:本期聚焦于小伙伴创作的《C++如何实现带权重的迪杰斯特拉最短路径算法实战》,敬请观看详情。带权重的最短路径计算是图论中的基础问题,迪杰斯特拉算法通过维护优先队列不断松弛边权来求出单源最短路。本文以C++实战为例,使用邻接表存储图结构,配合标准库priority_queue实现最小堆,详细演示从初始化距离数组到循环提取最小距离节点的完整流程。相比朴素写法,堆优化版本将时间复杂度降到O((V+E)logV),适合处理稀疏大图。文中还分析了负权边导致算法失效的原因,并给出用visited数组避免重复入堆的编码技巧,帮助读者写出健壮的路径求解代码。

在图论计算中,带权重的最短路径求解是路由规划、地图导航等系统的核心。迪杰斯特拉算法作为单源最短路的经典解法,要求图中所有边权非负,通过贪心策略每次锁定当前距离最小的节点,再以其为起点松弛相邻边。下面以C++为例,从图存储到堆优化实现完整走一遍。

一、图结构与权重表示

带权图通常使用邻接表而非邻接矩阵,尤其在节点多、边稀疏的场景下更省内存。每个节点关联一个边列表,边记录目标节点与权重。C++中可用vector嵌套结构体实现,这样既保持缓存友好,也方便遍历。

定义边结构体时,建议把权重声明为int或long long,避免浮点误差影响比较。若业务需要浮点权,应使用double并小心精度。下面的代码展示了最简化的图定义方式,后续算法都基于该结构操作。

#include <vector>
#include <iostream>
using namespace std;

struct Edge {
    int to;        // 目标节点
    int weight;    // 边权重,必须非负
};

// 邻接表:graph[u] 存放从u出发的所有边
vector<vector<Edge>> graph;

二、朴素迪杰斯特拉与堆优化区别

朴素实现每次扫描所有节点找最小距离,时间复杂度O(V^2),仅适合稠密图。实际工程多使用优先队列(最小堆)优化,把“取最小”交给堆,复杂度降为O((V+E)logV)。核心区别在于:堆中存的是“节点+当前距离”对,每次弹出堆顶即全局最小未确定节点。

需要注意的是,同一个节点可能因多次松弛而多次入堆,因此必须配合visited数组或在弹出时判断距离是否已过期。否则重复处理会让算法变慢甚至逻辑错误。下面的对比表列出两者关键差异:

实现方式取最小节点时间复杂度适用场景
朴素数组扫描线性查找O(V^2)边密、V较小
优先队列优化最小堆弹出O((V+E)logV)稀疏大图

三、C++堆优化实战代码

下面给出完整可编译的迪杰斯特拉实现。使用pair<int,int>存“距离,节点”,因priority_queue默认大顶堆,所以对距离取负或改用greater。代码先初始化距离为无穷大,源点距离置0并入堆,随后循环直至堆空。

松弛阶段遍历当前节点的所有出边,若新距离更小则更新并推入堆。visited数组在弹出时标记,保证每个节点只作为“确定最小”处理一次。该写法能稳妥应对节点重复入堆问题。

#include <vector>
#include <queue>
#include <climits>
#include <iostream>
using namespace std;

const int INF = INT_MAX;

// graph: 邻接表,n: 节点数,src: 源点
vector<int> dijkstra(const vector<vector<Edge>>& graph, int n, int src) {
    vector<int> dist(n, INF);
    vector<bool> visited(n, false);
    // 小顶堆:存 (-距离, 节点) 或改用 greater
    priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pq;

    dist[src] = 0;
    pq.push({0, src});

    while (!pq.empty()) {
        int u = pq.top().second;
        pq.pop();
        if (visited[u]) continue; // 过期条目跳过
        visited[u] = true;

        for (const Edge& e : graph[u]) {
            int v = e.to;
            int w = e.weight;
            if (dist[u] != INF && dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
                pq.push({dist[v], v});
            }
        }
    }
    return dist;
}

int main() {
    int n = 5;
    vector<vector<Edge>> g(n);
    // 示例边:0-1权4,0-2权1,2-1权2,1-3权1,2-3权5,3-4权3
    g[0].push_back({1, 4});
    g[0].push_back({2, 1});
    g[2].push_back({1, 2});
    g[1].push_back({3, 1});
    g[2].push_back({3, 5});
    g[3].push_back({4, 3});

    vector<int> d = dijkstra(g, n, 0);
    for (int i = 0; i < n; i++) {
        cout << "到节点 " << i << " 最短距离: " << d[i] << endl;
    }
    return 0;
}

四、负权边为何不可用

迪杰斯特拉依赖“已确定节点距离不再变更”的贪心前提。若存在负权边,可能从后续节点走出更短路径回头修正,但算法已标记该节点访问完成,于是得到错误结果。比如A到B权5,B到C权-4,A到C直连权2,朴素贪心会先锁C为2,却忽略A-B-C仅需1。

遇到负权场景应改用Bellman-Ford或SPFA。不过大多数实际业务如道路长度、传输延迟均为非负,因此迪杰斯特拉仍是首选。编码时可在建图阶段加断言防止误塞负权,提升调试效率。

提示:若需还原具体路径,可在松弛时额外记录pre数组,存前驱节点,结束后回溯即可。

五、工程化注意事项

在真实项目中,节点编号可能非连续或量级很大,可用unordered_map做映射。权重类型推荐统一用long long防溢出,特别是多段累加时。优先队列中存储距离建议用别名简化,例如using PII = pair<long long,int>。

另外,当图极稀疏且只需查单条路径时,A*算法结合启发式会更高效。但理解并写好基础迪杰斯特拉,是掌握一切最短路优化的根基。建议读者把上面代码跑通后,自行改为输出路径并测试不同图形。

Dijkstraweighted_graphC++修改时间:2026-08-03 08:12:29

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。