图的最短路径算法

0. 前言

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

1. 迪杰斯特拉 Dijkstra

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

算法步骤为:

  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]}

示例及图解:

D1.png
D2.png
D3.png
D4.png
D5.png

核心伪代码如下:

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)。其本质是动态规划

算法步骤为:

  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(NE)N 是点的个数,E 是边的个数。

算法步骤:

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

  2. 进行 N-1 次松弛操作,松弛操作即:遍历所有边,对于每一条边 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 次。

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

6.png

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

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

4. 参考

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容