图的最短路径算法


title: 图的最短路径算法
date: 2019-02-12 19:33:29


0. 前言

本文将介绍求解图最短路径的三个经典算法:迪杰斯特拉 Dijkstra、弗洛伊德 Floyd、贝尔曼-福特 Bellman-Ford。

1. 迪杰斯特拉 Dijkstra

迪杰斯特拉算法,用于解决 “给定起始点到其余点的最短路径” 问题,即单源最短路径算法。时间复杂度为 O ( n 2 ) O(n^2) O(n2)。其本质是贪心

算法步骤为:

  1. G[n][n] 二维数组记录图数据;定义 dis[n] 一维数组记录起始点到各点的最短路径,初始化为 INF(可以是 int 的最大值);visited[n] 一维数组记录该点是否给访问过(“访问过”表示已找到最短路径),初始化为 false
  2. 选择起始点 s ,令 dis[s] == 0
  3. 进行 n 次循环:
    1. 先从 dis[n] 数组的所有未访问结点中,找出最小值,并记录对应下标 p ,令 visited[p] = true
    2. 更新 p 所有邻接点在 dis[n] 数组中的值,更新规则为:dis[i] = min{dis[i], dis[p]+G[p][i]}

示例及图解:

1

2

3

4

5

核心伪代码如下:

int dis[n];
bool visited[n];
for (int i = 0; i < n; i++) {
    dis[i] = INF;
    visited[i] = false;
}

dis[s] = 0;
for (int j = 0; j < n; j++) {
    // 找 dis 数组中的最小值
    int p = -1, min = INF;
    for (int i = 0; i < n; i++) {
        if (visited[i] == false && dis[i] < min) {
            p = i;
            min = dis[i];
        }
    }
    visited[p] = true;

    // 更新最小值所有邻接点的值
    for (int i = 0; i < n; i++) {
        if (G[p][i] == INF || visited[i]) continue;
        if (dis[i] > dis[p]+G[p][i]) {
            dis[i] = dis[p] + G[p][i];
        }
    }
}

2. 弗洛伊德 Floyd

弗洛伊德是求解图中任意两点间最短路径的算法。时间复杂度为 O ( n 3 ) O(n^3) O(n3)。其本质是动态规划

算法步骤为:

  1. 任意两点间的最短距离用 d(x,y) 表示,初始值为两点相连边的权重。

  2. 遍历所有点 k,若任意两点 i 和 j,满足 d(i,j) > d(i,k) + d(k,j),则 d(i,j) = d(i,k) + d(k,j)

代码如下:

for (k = 1; k <= n; k++) {
    for (i = 1; i <= n; i++) {
        for (j = 1; j <= n; j++) {
            if (d[i][j] > d[i][k] + d[k][j]) {
                d[i][j] = d[i][k] + d[k][j];
            }
        }
    }
}

算法分析:Floyd 的核心思想是动态规划。

  1. 我们先定义状态:d[k][i][j],它表示经过前 k 个节点,点 i 到点 j 的最短路径。

  2. d[k][i][j] 可以由 d[k-1][i][j] 转移而来:

    • 假设已经求出,经过前 k-1 个节点,任意两点间的最短路径。
    • 那么,d[k][i][j] 就是 经过前 k-1 个节点 i 到 j 最短路径经过第 k 个节点 i 到 j 最短路径 中的最小值。
    • 而经过第 k 个节点 i 到 j 最短路径,就是 i 到 k 的最短路径加上 k 到 j 的最短路径。
    • 最终,得出状态转移方程为:d[k][i][j] = min{d[k-1][i][j], d[k-1][i][k] + d[k-1][k][j]}
  3. 由于 d[k][x][x] 的状态仅由 d[k-1][x][x] 转移而来,所以我们可以进行优化:d[i][j] = min{d[i][j], d[i][k] + d[k][j]}

3. 贝尔曼-福特 Bellman-Ford

贝尔曼-福特算法,也是一个单源最短路径算法,同时它还能处理负权边。算法时间复杂度为 O ( N E ) O(NE) O(NE) N N N 是点的个数, E E E 是边的个数。

算法步骤:

  1. 令源点为 s ,源点到任意点 x 的最短距离用 d(x) 表示。d(s) 初始值为0,其余初始值为无穷。

  2. 进行 N − 1 N-1 N1 次松弛操作,松弛操作即:遍历所有边,对于每一条边 e(i,j) ,如果存在 d(j) > d(i) + e(i,j) ,则令 d(j) = d(i) + e(i,j)

代码如下:

for (i = 0; i < n-1; i++) {
    for (j = 0; j < E; j++) {
        if (d(e[j].to) > d(e[j].from) + e[j]) {
            d(e[j].to) = d(e[j].from) + e[j];
        }
    }
}

算法分析:松弛操作的过程十分神奇,直觉告诉我它肯定是正确的,但具体原因我也是一头雾水。不过,我们可以知道,每次松弛操作后,至少能确定一个点的最短路径。所以,需要进行 N − 1 N-1 N1 次。

Bellman-Ford 如何解决 Dijkstra 不能解决的负权边问题呢?如下图,源点为 1 。若在 Dijkstra 中,第二次大循环时便会确定源点到点 3 的最短距离为 1 ;而在 Bellman-Ford 中,经过松弛操作便可以确定源点到点 3 的最短距离为 -1 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WK22BmN2-1611931391795)(图的最短路径算法/6.png)]

Bellman-Ford 算法虽然能解决负权边的问题,但时间复杂度还是偏高,当用于稠密图时,是无法接受的。

因此,有人提出了 Bellman-Ford 的优化算法:SPFA。即第一次松弛操作,只需要对源点的邻接边进行即可;第二次松弛操作,只需要对与这些边相连点的邻接边进行即可;以此类推,直至所有边遍历完。这类似于 BSF 。

4. 参考

【原创】算法系列——四种最短路算法:Floyd,Dijkstra,Bellman-Ford,SPFA

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dounineli

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值