跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

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

图的最短路径问题是经典算法题。Dijkstra 适用于非负权单源,采用贪心策略;Bellman-Ford 处理负权边并检测负环,通过多次松弛实现;Floyd 解决全源最短路径,利用动态规划思想。通过松弛操作更新距离,配合前驱数组还原路径。掌握这三种算法能应对不同场景下的路径规划需求。

菩提发布于 2026/3/23更新于 2026/5/74 浏览
C++ 图论实战:深入理解三种最短路径算法

问题背景

最短路径问题是图论中的经典场景。在带权有向图中,我们需要找到从某个起点到目标顶点的路径,使得路径上所有边的权值总和最小。根据应用场景的不同(如单源、全源、是否存在负权边),我们有几种不同的策略。

Dijkstra 算法:贪心策略的单源解法

Dijkstra 算法是解决非负权图单源最短路径的经典方案。它的核心思想是贪心:每次从未确定最短路径的节点中,选出当前距离起点最近的一个,标记为'已确定',并用它去松弛(更新)所有邻接点的距离估计值。

需要注意的是,该算法不能处理包含负权边的图。一旦存在负权边,贪心选择的最短路径可能会被后续更小的负权边推翻,导致结果错误。

Dijkstra 算法执行过程示意图

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

    // S 集合:已经确定最短路径的顶点集合
    std::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] = ;

        
         ( v = ; v < n; ++v) {
            
             (!S[v] && _matrix[u][v] != MAX_W && dist[u] + _matrix[u][v] < dist[v]) {
                dist[v] = dist[u] + _matrix[u][v];
                pPath[v] = u;
            }
        }
    }
}
static_cast
int
// 标记 u 为已确定
true
// 2. 松弛操作:用 u 更新其邻接点 v 的距离
for
size_t
0
// 如果 v 未确定,且 u->v 连通,且经过 u 更近
if

在实际运行中,你会发现这个双重循环结构决定了它的时间复杂度较高。对于稠密图,使用邻接矩阵配合线性扫描找最小值是比较直观的写法;如果是稀疏图,通常会引入优先队列来优化查找 min 的过程。

Bellman-Ford 算法:处理负权与检测负环

当图中出现负权边时,Dijkstra 就失效了。Bellman-Ford 算法能解决这个问题,但代价是更高的时间复杂度。它的本质是暴力松弛:对图中的所有边重复进行松弛操作,最多进行 $n$ 轮($n$ 为顶点数)。因为任何不含负环的最短路径最多只经过 $n-1$ 条边。

跑完 $n$ 轮后,再扫一遍所有边,如果还能更新距离,说明图中存在负权回路,此时最短路径不存在。

Bellman-Ford 算法执行过程示意图

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

    // 初始化距离和前驱数组
    dist.resize(n, MAX_W);
    pPath.resize(n, -1);
    dist[srci] = W(); // 起点距离设为 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 + 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] = static_cast<int>(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;
}

这里有个小细节:我们在循环内部加了 if (!update) break;。这其实是一个常见的优化,因为在某些情况下,可能不需要跑满 $n$ 轮就能提前收敛,这样能节省一些不必要的计算。

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

如果你需要知道图中任意两个顶点之间的最短距离,而不是指定一个起点,Floyd-Warshall 算法是最直接的选择。它基于动态规划的思想,依次把每个点当作中转点,判断从 $i$ 到 $j$ 是直接走更近,还是经过中转点 $k$ 再走更近。

三层循环结束后,我们就得到了全图的最短路径矩阵。

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

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

    // 2. 直接相连的边更新一下
    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] = static_cast<int>(i); // 前驱设为 i
            }
            if (i == j) {
                vvDist[i][j] = W(); // 自己到自己距离为 0
            }
        }
    }

    // 3. 动态规划更新:k 作为中间点尝试去更新 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 更近,则更新
                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];
                    // 更新前驱路径
                    vvpPath[i][j] = vvpPath[k][j];
                }
            }
        }
    }
}

关于路径还原,代码中 vvpPath[i][j] = vvpPath[k][j] 这一行很关键。它的意思是,如果 $i$ 到 $j$ 的最短路径经过了 $k$,那么 $j$ 的前驱就是 $k$ 到 $j$ 路径上的前驱。通过递归或迭代查询 pPath 数组,我们就能完整还原出路径序列。

这三种算法各有千秋:Dijkstra 快但不能抗负权,Bellman-Ford 能抗负权但慢,Floyd 适合全源但空间开销大。实际开发中,根据数据规模和约束条件选择合适的工具,往往比单纯追求算法复杂度更重要。

目录

  1. 问题背景
  2. Dijkstra 算法:贪心策略的单源解法
  3. Bellman-Ford 算法:处理负权与检测负环
  4. Floyd-Warshall 算法:任意两点间的最短路径
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • OpenClaw 与 Discord 多 Agent 多频道配置指南
  • 鸿蒙 WebView 混合开发:Web 组件内部跨域问题的客户端解决方案
  • DGX Spark 部署 Stable Diffusion 3.5 与 ComfyUI 实战
  • Python 技能变现指南:合法合规的兼职方向与技术实践
  • 免费版 Trae 编辑器实测:排队机制与潜在风险
  • Python 数学可视化:显函数、隐函数及复杂曲线交互绘图
  • Java 模拟算法题目练习
  • LLaMA-Factory 微调实战:关键超参数选择指南
  • 洪水填充算法与 DFS/BFS 应用总结
  • AI 时代技术民主化:文科生为何成最大受益者?
  • 如何转行成为产品经理?
  • Cursor 2.0 发布:自研 Composer 模型上线及多 Agent 实测
  • Vue Skills:让 AI 掌握 Vue 最佳实践
  • Web 团队开发 App 是否适合选择 Capacitor
  • 基于 Cogito-V1-Preview-Llama-3B 的微信小程序 AI 对话集成指南
  • 多大模型 API 统一调用方案:主流开源项目推荐
  • 滑动窗口算法实战:水果成篮与最小覆盖子串
  • Python 类(Class)语法、技巧与实战案例
  • 大疆 MSDK 实现无人机视觉引导自适应降落
  • 6 层高速 PCB 设计实战:立创逻辑派 FPGA-G1 开发板笔记

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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