跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 图论实战:深入理解三种经典最短路径算法

C++ 图论中最短路径问题通常涉及三种核心算法。Dijkstra 基于贪心策略,适合非负权图的单源计算,效率较高但无法处理负权边;Bellman-Ford 通过多次松弛操作支持负权边并检测负权回路,代价是时间复杂度略高;Floyd-Warshall 利用动态规划实现全源最短路径求解。开发者需根据图中是否存在负权边及计算范围需求来选择合适的算法实现。

BigDataPan发布于 2026/3/28更新于 2026/10/1781 浏览
C++ 图论实战:深入理解三种经典最短路径算法

C++ 图论实战:深入理解三种经典最短路径算法

最短路径问题是图论中的核心议题,旨在从带权有向图中找到两点间权值总和最小的路径。在实际工程中,根据图的特性(如是否存在负权边)和计算需求(单源或全源),我们需要选择合适的算法。

Dijkstra 算法:非负权图的贪心策略

Dijkstra 是解决单源最短路径的经典算法,采用贪心思想。它仅适用于没有负权边的图。算法维护一个已确定最短路径的集合,每次从未确定的节点中选出距离起点最近的点,用它去松弛邻接点的距离估计值,直到所有节点都被处理。

Dijkstra 算法执行过程示意图

实现时需要注意初始化距离数组为无穷大,并将起点的距离设为 0。松弛操作是核心:如果 dist[u] + weight(u, v) < dist[v],则更新 v 的距离和前驱节点。

// src 是选定的起点,dist 记录起点到各点的最短路径,pPath 记录前驱顶点下标
void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath) {
    size_t srci = GetVertexIndex(src);
    size_t n = _vertexs.size();
    
    // 初始化距离和前驱
    dist.resize(n, MAX_W);
    pPath.resize(n, -1);
    dist[srci] = 0;
    pPath[srci] = srci;

    // S 标记已确定最短路径的顶点集合
    vector<bool> S(n, false);

    for (size_t j = 0; j < n; ++j) {
        // 1. 在未确定集合中选距离最小且未访问的节点 u
        int u = 0;
        W min_dist = MAX_W;
        for (size_t i = 0; i < n; ++i) {
            if (!S[i] && dist[i] < min_dist) {
                u = i;
                min_dist = dist[i];
            }
        }
        
        S[u] = true;

        // 2. 松弛更新 u 连接的所有顶点 v
        for (size_t v = 0; v < n; ++v) {
            if (!S[v] && _matrix[u][v] != MAX_W && dist[u] + _matrix[u][v] < dist[v]) {
                dist[v] = dist[u] + _matrix[u][v];
                pPath[v] = u;
            }
        }
    }
}

实际运行时,若使用优先队列优化,时间复杂度可降至 $O(E \log V)$,但在稠密图中简单的数组扫描往往更直观高效。

Bellman-Ford 算法:处理负权边与回路检测

当图中存在负权边时,Dijkstra 失效,此时应选用 Bellman-Ford 算法。它的本质是对所有边进行多次松弛,重复 $n$ 轮($n$ 为顶点数)。因为任何不含负权环的最短路径最多经过 $n-1$ 条边。

Bellman-Ford 算法松弛过程

该算法不仅能算出单源最短路径,还能在最后一轮松弛后检查是否还能更新距离。如果能更新,说明图中存在负权回路,最短路径不存在。

bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath) {
    size_t n = _vertexs.size();
    size_t srci = GetVertexIndex(src);

    // 初始化
    dist.resize(n, MAX_W);
    pPath.resize(n, -1);
    dist[srci] = 0;

    // 总体最多更新 n 轮
    for (size_t k = 0; k < n; ++k) {
        bool update = false;
        // cout << "更新第:" << k << "轮" << endl; // 调试时可开启

        for (size_t i = 0; i < n; ++i) {
            for (size_t j = 0; j < n; ++j) {
                // 尝试松弛 srci -> i -> j
                if (_matrix[i][j] != MAX_W && dist[i] != MAX_W && dist[i] + _matrix[i][j] < dist[j]) {
                    update = true;
                    dist[j] = dist[i] + _matrix[i][j];
                    pPath[j] = i;
                }
            }
        }
        // 优化:如果本轮没有更新,提前结束
        if (!update) break;
    }

    // 检查负权回路
    for (size_t i = 0; i < n; ++i) {
        for (size_t j = 0; j < n; ++j) {
            if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j]) {
                return false; // 存在负权回路
            }
        }
    }
    return true;
}

虽然逻辑简单,但 Bellman-Ford 的时间复杂度为 $O(V \cdot E)$,在处理大规模图时性能不如 SPFA 或 Dijkstra,但在需要检测负环的场景下它是首选。

Floyd-Warshall 算法:任意两点间最短路径

Floyd-Warshall 算法用于求解任意两点之间的最短路径。它采用动态规划思想,依次将每个点作为中转点,判断从 $i$ 到 $j$ 是直接走更近,还是经过中转点 $k$ 再走更近。

三层循环跑完即可得到全图最短路径矩阵。代码结构紧凑,适合顶点数较少(通常 $V \le 500$)的场景。

void FloydWarshall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath) {
    size_t n = _vertexs.size();
    vvDist.resize(n);
    vvpPath.resize(n);

    // 初始化权值和路径矩阵
    for (size_t i = 0; i < n; ++i) {
        vvDist[i].resize(n, MAX_W);
        vvpPath[i].resize(n, -1);
    }

    // 直接相连的边更新一下
    for (size_t i = 0; i < n; ++i) {
        for (size_t j = 0; j < n; ++j) {
            if (_matrix[i][j] != MAX_W) {
                vvDist[i][j] = _matrix[i][j];
                vvpPath[i][j] = i;
            }
            if (i == j) {
                vvDist[i][j] = 0;
            }
        }
    }

    // 最短路径的更新:i -> {其他顶点} -> j
    for (size_t k = 0; k < n; ++k) {
        for (size_t i = 0; i < n; ++i) {
            for (size_t j = 0; j < n; ++j) {
                // k 作为中间点尝试去更新 i->j 的路径
                if (vvDist[i][k] != MAX_W && vvDist[k][j] != MAX_W && 
                    vvDist[i][k] + vvDist[k][j] < vvDist[i][j]) {
                    vvDist[i][j] = vvDist[i][k] + vvDist[k][j];
                    // 记录路径:如果 k->j 直接相连,上一个点就是 k;否则继承 k->j 的前驱
                    vvpPath[i][j] = vvpPath[k][j];
                }
            }
        }
    }
}

总结来说,选择算法时要看场景:无负权求单源用 Dijkstra,有负权求单源用 Bellman-Ford,求全源且点数不多用 Floyd。理解它们的底层逻辑,比死记硬背代码更重要。

目录

  1. C++ 图论实战:深入理解三种经典最短路径算法
  2. Dijkstra 算法:非负权图的贪心策略
  3. Bellman-Ford 算法:处理负权边与回路检测
  4. Floyd-Warshall 算法:任意两点间最短路径

更多推荐文章

查看全部
  • Copilot Plan Mode 与多模型协同实战:复杂项目重构指南
  • HTML5 结合 AI 实现智能场景渲染与交互实践
  • MC.JS WEBMC1.8 入门:10 分钟构建首个方块世界
  • 前端数据可视化工具选型指南:如何匹配需求与性能
  • Seq2Seq 模型实战:ScheduledEmbeddingTrainingHelper 原理与使用
  • Windows 系统安装 MySQL 8.0 图文教程
  • Java Thread 类创建线程的多种方式与线程终止详解
  • 双显卡环境下 LLaMA-Factory 大模型微调环境搭建
  • C++ mio 库内存映射文件 IO 使用指南
  • C++ 引用、内联函数与 nullptr 详解
  • Unix 进程控制基础:权限、解释器与调度
  • Cursor、Claude Code 与 GitHub Copilot:三款 AI 编程工具深度对比
  • Python 自动化办公与数据分析入门指南
  • 在飞书中接入 OpenClaw 打造专属 AI 助手
  • 基于 SpringBoot+Vue 的海洋生物管理系统设计与实现
  • Stable Diffusion 皮革服装 LoRA 模型部署与使用指南
  • Python+AI 实战:搭建本地智能问答机器人
  • Kubernetes Gateway API 高级流量路由实战
  • 基于 Arduino 与 6.5 寸轮毂电机的智能动态跟随机器人底盘设计
  • Obsidian Copilot API 密钥配置实战:OpenRouter、Gemini 与 OpenAI 集成

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online