图的应用——最短路径(Dijkstra算法和Floyd算法)

广度优先搜索(Breadth-First-Search, BFS)查找最短路径只针对无权图,而当图是带权图时,把从一个顶点 v0 到图中其余任意一个顶点 vi 的一条路径所经过边上的权值之和,定义为该路径的带权路径长度,把带权路径长度最短的那条路径(可能不止一条)称为最短路径。

带权有向图G的最短路径问题一般可分为两类:
①单源最短路径:即求图中某一顶点到其他各个顶点的最短路径,可通过Dijkstra算法求解;
②求解每对顶点间的最短路径,可通过Floyd算法求解。
下面将介绍这两种算法:

一、Dijkstra算法求单源最短路径问题

1、Dijkstra算法的思想:

Dijkstra算法设置一个集合V记录所有顶点,集合S记录已求得的最短路径的顶点,初始时把源点 v0 放入S,集合S每并入一个新顶点 vi ,都要修改源点 v0 到集合V-S(集合V与集合S的差)中顶点当前的最短路径长度值。在构造过程中还设置了三个辅助数组:

  • final[ ]:标记各顶点是否已找到最短路径,即是否归入集合S;
  • dist[ ]:记录从源点 v0 到其他各顶点当前的最短路径长度,它的初始值为:若从 v0 到 vi 有弧,则 dist[i] 为弧上的权值,否则置 dist[i] 为 ∞;
  • path[ ]:path[i] 表示从源点到顶点 i 之间的最短路径的前驱结点,在算法结束时,可根据其值追溯得到源点 v0 到顶点 vi 的最短路径。

2、Dijkstra算法的步骤:

假设从顶点0出发,即 v0 = 0,集合S最初只包含顶点0,邻接矩阵arcs表示带权有向图,arcs[i][j] 表示有向边<i, j>的权值,若不存在有向边<i, j>,则 arcs[i][j] 为 ∞。Dijkstra算法的步骤为:

(不考虑对path[ ]的操作)
① 初始化:集合S初始为{0},dist[ ]的初始值 dist[i] = arcs[0][i],其中 i = 1,2,…,n-1;
② 从顶点集合V-S中选出 vj,满足 dist[j] = Min{ dist[i] | vi∈V-S },vj 就是当前求得的一条从 v0 出发的最短路径的终点,令 S = S∪{ j }。
③ 修改从 v0 出发到集合V-S上任意一个顶点 vk 可达的最短路径长度:若 dist[j] + arcs[j][k] < dist[k],则更新 dist[k] = dist[j] + arcs[j][k]。
④ 重复②-③的步骤共n-1次,直到所有的顶点都包含在集合S中。

步骤说的很复杂,下面我们直接来看示例进行理解。

3、Dijkstra算法的示例:

对下图应用Dijkstra算法,求从顶点1出发至其余顶点的最短路径长度:

从v1到各个顶点的dist值和最短路径长度的求解过程如下图所示:

下面进行详细的解释说明:

1)初始化:集合S初始为{v1},v1 可达 v2 和 v5、不可达 v3 和 v4,因此 dist[ ] 数组各元素的初始值如下图所示。其中 path[ ] 数组存储从v1到各顶点之间的最短路径的前驱结点;final[ ] 数组标记各顶点是否已经归入集合S,若已归入令final值为1,否则为0。

2)第一轮:从上图选出集合V-S中dist值最小的顶点(即v5),将其并入集合S(S = {v1, v5})并将v5的final值设为1,即此时已找到 v1 到 v5 的最短路径。当 v5 加入S后,从 v1 到集合V-S中可达顶点的最短路径长度可能会产生变化,因此需要更新 dist[ ] 数组和 path[ ] 数组。更新后各元素的dist值和path值如下图所示:

① v5 可达 v2:v1–>v5–>v2 的距离为8,比 v1–>v2 的距离(dist[2] = 10)小,因此更新 dist[2] = 8、path[2] = v5
② v5 可达 v3:v1–>v5–>v3 的距离为14,比 dist3] = ∞ 小,因此更新 dist[3] = 14、path[3] = v5
③ v5 可达 v4:v1–>v5–>v4 的距离为7,比 dist[4] = ∞ 小,因此更新 dist[4] = 7、path[4] = v5

3)第二轮:从上图选出集合V-S中dist值最小的顶点(即v4),将其并入集合S(S = {v1, v5, v4})并将v4的final值设为1。继续更新 dist[ ] 数组和 path[ ] 数组。更新后各元素的dist值和path值如下图所示:

① v4 不可达 v2:dist[2] 和 path[2] 的值不变
② v4 可达 v3:v1–>v5–>v4–>v3 的距离为13,比 dist[3] = 14 小,因此更新 dist[3] = 13、path[3] = v4

4)第三轮:从上图选出集合V-S中dist值最小的顶点(即v2),将其并入集合S(S = {v1, v5, v4, v2})并将v2的final值设为1。继续更新 dist[ ] 数组和 path[ ] 数组。更新后各元素的dist值和path值如下图所示:

① v2 可达 v3:v1–>v5–>v2–>v3 的距离为9,比 dist[3] = 13 小,因此更新 dist[3] = 9、path[3] = v2

5)第四轮:从上图选出集合V-S中dist值最小的顶点(只剩下v3),将其并入集合S(S = {v1, v5, v4, v2, v3})并将v3的final值设为1。此时全部顶点都已经在集合S中了。

此时源点 v1 到其余各顶点的最短路径长度就已经求出来了,通过逆推 path[i] 还可以得到从源点 v1 到顶点 vi 之间的最短路径会经过哪些结点。

4、Dijkstra算法的时间复杂度:

是用邻接矩阵表示时,时间复杂度为O(|V|2)。使用带权的邻接表表示时,虽然修改 dist[ ] 的时间可以减少,但由于在 dist[ ] 中选择最小分量的时间不变,所以时间复杂度仍为O(|V|2)。

【注意!】边上带有负权值时,Dijkstra算法并不适用。如下图所示的带权有向图,利用Dijkstra算法不一定能得到正确的结果。

5、Dijkstra算法与Prim算法的异同之处:

Dijkstra算法的流程、操作与Prim的算法的都很相似,都基于贪心策略。区别在于:

  • 目的不同:Dijkstra算法的目的是构建单源点的最短路径树;Prim算法的目的是构建最小生成树。
  • 算法思路略有不同:Dijkstra算法每次找出到源点距离最近且未归入集合的点,并把它归入集合,同时以这个点为基础更新从源点到其他所有顶点的距离;Prim算法从一个点开始,每次选择权值最小的边,将其连接到已构建的生成树上,直至所有顶点都已加入。
  • 适用的图不同Dijkstra算法可用于带权有向图或带权无向图;Prim算法只能用于带权无向图。

图的应用——最小生成树(Prim算法和Kruskal算法)

6、例题:

D(在Dijkstra算法的执行过程中,只可能修改从源点到集合V-S中某个顶点的最短路径)

C

B
在这里插入图片描述

21, 3, 14, 6

二、Floyd算法求各顶点之间最短路径问题

1、Floyd算法的思想:

求所有顶点之间的最短路径问题描述如下:已知一个各边权值均大于0的带权有向图,对任意两个顶点 vi 不等于 vj ,要求求出 vi 与 vj 之间的最短路径和最短路径长度。

Floyd算法的基本思想是:递推产生一个n阶方阵序列 A(-1), A(0), … , A(k), … , A(n-1),其中 A(k) [i][j] 表示从顶点 vi 到顶点 vj 的路径长度,k 表示绕行第 k 个顶点的运算步骤。

2、Floyd算法的步骤:

① 初始时,对于任意两个顶点 vi 和 vj ,若它们之间存在边,则以此边上的权值作为它们之间的最短路径长度;若它们之间不存在有向边,则以∞作为它们之间的最短路径长度。
② 逐步在原路径中加入顶点 k(k = 0, 1, … , n-1)作为中间顶点,若增加中间顶点后,得到的路径比原来的路径长度减少了,则以此新路径代替原路径。

算法描述为:
定义一个n阶方阵序列 A(-1), A(0), … , A(k), … , A(n-1),其中 :
A(-1) [i][j] = arcs[i][j] ;
A(k) [i][j] = Min{ A(k-1) [i][j] , A(k-1) [i][k]+A(k-1) [k][j] },k = 0, 1, … , n-1。
上述式子中,A(0) [i][j] 是从顶点 vi 到 vj、中间顶点是 v0 的最短路径的长度;A(k) [i][j] 是从顶点 vi 到 vj、中间顶点的序号不大于k的最短路径的长度。

Floyd算法是一个迭代的过程,每迭代一次,在从 vi 到 vj 的最短路径上就多考虑了一个顶点,经过n次迭代后,所得到的 A(n-1) [i][j] 就是 vi 到 vj 的最短路径长度,即方阵 A(n-1) 中就保存了任意一对顶点之间的最短路径长度。

步骤说的很复杂,下面我们直接来看示例进行理解。

3、Floyd算法的示例:

对下图的带权有向图G及其邻接矩阵arcs应用Floyd算法,求所有顶点之间的最短路径长度:

各个顶点之间的最短路径长度的求解过程如下图所示:

下面进行详细的解释说明:

1)初始化:方阵 A(-1) [i][j] = arcs[i][j],如下图所示:
由于图G为简单图(不存在重复边,且不存在顶点到自身的边),因此对角线上的元素均为0。

2)第一轮:将 v0 作为中间顶点,对于所有顶点对 {i, j},若有 A(-1) [i][j] > A(-1) [i][0] + A(-1) [0][j],则将 A(-1) [i][j] 更新为 A(-1) [i][0] + A(-1) [0][j] 。由于是以顶点 v0 作为“中转站”,因此顶点 v0 所在行和列的值均不会发生变化,对角线元素的值也是如此,所以可能会改变值的元素只有 A(-1) [1][2] 和 A(-1) [2][1] 。

① 比较 v2–>v1 和 v2–>v0–>v1 的权值大小:
A(-1) [2][1](= ∞)> A(-1) [2][0] + A(-1) [0][1](= 11),因此更新 A(-1) [2][1] 的值为11。
② 比较 v1–>v2 和 v1–>v0–>v2 的权值大小:
A(-1) [1][2](= 4)< A(-1) [1][0] + A(-1) [0][2](= 23),因此不改变 A(-1) [1][2] 的值。

更新后的方阵标记为 A(0),如下图所示:

3)第二轮:将 v1 作为中间顶点,对于所有顶点对 {i, j},若有 A(0) [i][j] > A(0) [i][1] + A(0) [1][j],则将 A(0) [i][j] 更新为 A(0) [i][1] + A(0) [1][j] 。顶点 v1 所在行和列的值以及对角线元素的值均不会发生变化,因此可能会改变值的元素只有 A(0) [2][0] 和 A(0) [0][2] 。

① 比较 v0–>v2 和 v0–>v1–>v2 的权值大小:
A(0) [0][2](= 13)> A(0) [0][1] + A(0) [1][2](= 10),因此更新 A(0) [2][1] 的值为10。
② 比较 v2–>v0 和 v2–>v1–>v0 的权值大小:
A(0) [2][0](= 5)< A(0) [2][1] + A(0) [1][0](= 21),因此不改变 A(0) [2][0] 的值。

更新后的方阵标记为 A(1),如下图所示:

4)第三轮:将 v2 作为中间顶点,对于所有顶点对 {i, j},若有 A(1) [i][j] > A(1) [i][2] + A(1) [2][j],则将 A(1) [i][j] 更新为 A(1) [i][2] + A(1) [2][j] 。顶点 v2 所在行和列的值以及对角线元素的值均不会发生变化,因此可能会改变值的元素只有 A(1) [1][0] 和 A(0) [0][1] 。

① 比较 v1–>v0 和 v1–>v2–>v0 的权值大小:
A(1) [1][0](= 10)> A(1) [1][2] + A(1) [2][0](= 9),因此更新 A(1) [1][0] 的值为9。
② 比较 v0–>v1 和 v0–>v2–>v1 的权值大小:
A(1) [0][1](= 6)< A(1) [0][2] + A(1) [2][1](= 21),因此不改变 A(1) [0][1] 的值。

更新后的方阵标记为 A(2),如下图所示:

此时各顶点之间最短路径长度就已经求出来了。

4、Floyd算法的时间复杂度:

Floyd算法的时间复杂度为O(|V|3)。不过由于其代码很紧凑,且并不包含其他复杂的数据结构,因此隐含的常数系数是很小的,即使对于中等规模的输入来说,它仍然是相当有效的。

也可以用单源最短路径算法来解决每对顶点之间的最短路径问题,轮流将每个顶点作为源点,并且在所有边权值均非负时,运行一次Dijkstra算法,其时间复杂度为O(|V|2)×|V|=O(|V|3)。

【注意!】Floyd算法允许图中有带负权值的边,但不允许有包含带负权值的边组成的回路Floyd算法同样适用于带权无向图,因为带权无向图可视为权值相同往返二重边的有向图。

5、例题:

A

C(注意B选项中的“每对顶点间”)

三、求最短路径算法的总结

BFS算法、Dijkstra算法和Floyd算法求最短路径的总结如下图所示:

采用不同存储结构时BFS算法、Dijkstra算法和Floyd算法的时间复杂度如下图所示:(其中n为顶点数,e为边数)

  • 32
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值