关键字:最短路径;
最短路径算法;Floyd 算法;Dijkstra 算法;Bellman-Ford 算法;SPFA 算法
1.最短路径概述
最短路径问题是指在一个赋权图的两个节点之间找出一条具有最小权的路径, 这是图论 的描述, 也是图论中研究的一个重要问题。 现实生活中我们可以看到这些最短路径问题的例 子,公交车辆的最优行驶路线和旅游线路的选择等;军事领域中也有应用,作战部队的行军 路线等问题就与寻找一个图的最短路径密切相关, 因此对最短路径问题的深入研究和广泛应 用具有重要意义和实用价值。 在线路优化问题中,如果优化指标与路程的相关性较强,而和其他因素相关性较弱时, 即以最短路程为准则,则考虑转化为最短路径问题。比如军事行军线路选取时,假如从出发 地到目的地之间有多种线路可以选取, 危险指数在预测概率相等时, 就要考虑最短路径问题。
2.最短路径算法
最短路径算法问题是图论研究中的一个经典算法问题, 旨在寻找图(由结点和路径组 成的)中两结点之间的最短路径。 算法具体的形式包括: 确定起点的最短路径问题 - 即已知起始结点,求最短路径的问题。 确定终点的最短路径问题 - 与确定起点的问题相反,该问题是已知终结结点,求最短 路径的问题。 在无向图http://write.blog.youkuaiyun.com/postedit?ref=toolbar中该问题与确定起点的问题完全等同, 在有向图中该问题等同于把所 有路径方向反转的确定起点的问题。 确定起点终点的最短路径问题 - 即已知起点和终点,求两结点之间的最短路径。 全局最短路径问题 - 求图中所有的最短路径。
3.Floyd 算法
3.1 算法定义
Floyd 算法是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最 短路径问题,同时也被用于计算有向图的传递闭包。Floyd 算法的时间复杂度为 O(N3),空 间复杂度为 O(N2)。
3.2 算法思想原理
Floyd 算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是 寻找从点 i 到点 j 的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个 诠释。 从任意节点 i 到任意节点 j 的最短路径不外乎 2 种可能,1 是直接从 i 到 j,2 是从 i 经 过若干个节点 k 到 j。所以,我们假设 Dis(i,j)为节点 u 到节点 v 的最短路径的距离,对于每 一个节点 k,我们检查 Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从 i 到 k 再到 j 的路径比 i 直接到 j 的路径短,我们便设置 Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍 历完所有节点 k,Dis(i,j)中记录的便是 i 到 j 的最短路径的距离。
3.3算法过程描述
a.从任意一条单边路径开始。 所有两点之间的距离是边的权, 如果两点之间没有边相连, 则权为无穷大。初始化
b.对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知 的路径更短。如果是更新它。更新
3.4 算法适用范围
⑴ APSP(All Pairs Shortest Paths);
⑵ 稠密图效果最佳;
⑶ 边权可正可负
3.5 核心代码
void Floyd()
{
int i, j, k;
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
{
for (k = 1; k <= n; k++)
{
map[j][k] = min(map[j][k], max(map[j][i], map[i][k]));
}
}
}
printf("Frog Distance = %.3f\n", map[1][2]);
}
4.Dijkstra 算法
4.1 算法定义
Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法, 用于计算一个节点到其他所有节 点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra 算法是很有代表性的最短路径算法, 在很多专业课程中都作为基本内容有详细的介绍, 如数 据结构,图论,运筹学等等。注意该算法要求图中不存在负权边。 问题描述: 在无向图 G=(V,E) 中, 假设每条边 E[i] 的长度为 w[i], 找到由顶点 V0 到 其余各点的最短路径。
4.2 算法思想原理
设 G=(V,E)是一个带权有向图,把图中顶点集合 V 分成两组,第一组为已求出最短路径 的顶点集合(用 S 表示,初始时 S 中只有一个源点,以后每求得一条最短路径 , 就将加入 到集合 S 中,直到全部顶点都加入到 S 中,算法就结束了) ,第二组为其余未确定最短路径 的顶点集合(用 U 表示) ,按最短路径长度的递增次序依次把第二组的顶点加入 S 中。在加 入的过程中,总保持从源点 v 到 S 中各顶点的最短路径长度不大于从源点 v 到 U 中任何顶 点的最短路径长度。此外,每个顶点对应一个距离,S 中的顶点的距离就是从 v 到此顶点的 最短路径长度, U 中的顶点的距离, 是从 v 到此顶点只包括 S 中的顶点为中间顶点的当前最 短路径长度。
4.3 算法过程描述
a.初始时, S 只包含源点, 即 S={v}, v 的距离为 0。 U 包含除 v 外的其他顶点, 即:U={其 余顶点}, 若 v 与 U 中顶点 u 有边, 则<u,v>正常有权值, 若 u 不是 v 的出边邻接点, 则<u,v> 权值为∞。
b.从 U 中选取一个距离 v 最小的顶点 k,把 k,加入 S 中(该选定的距离就是 v 到 k 的 最短路径长度) 。
c.以 k 为新考虑的中间点,修改 U 中各顶点的距离;若从源点 v 到顶点 u 的距离(经过 顶点 k)比原来距离(不经过顶点 k)短,则修改顶点 u 的距离值,修改后的距离值的顶点 k 的距离加上边上的权。
d.重复步骤 b 和 c 直到所有顶点都包含在 S 中。
4.4 算法适用范围
⑴ 单源最短路径;
⑵ 有向图和无向图;
⑶ 所有边权非负。
4.5 核心代码
void Dijkstra()
{
int v, i, j;
int min;
for (i = 1; i <= m; i++)
{
dist[i] = map[1][i];
vis[i] = 0;
}
vis[1] = 1;
for (i = 1; i <= m; i++)
{
min = INF;
for (j = 1; j <= m; j++)
{
if (!vis[j] && dist[j] < min)
{
v = j;
min = dist[j];
}
}
vis[v] = 1;
for (j = 1; j <= m; j++)
{
if (!vis[j] && dist[j] > map[v][j] + dist[v])
dist[j] = map[v][j] + dist[v];
}
}
printf("%d\n", dist[m]);
}
5.Bellman-Ford 算法
5.1 算法定义
Bellman-Ford 算法--能在更普遍的情况下(存在负权边)解决单源点最短路径问题。不 允许边的权是负权,如果遇到负权,则可以采用 Bellman-Ford 算法.算法大致流程是用一个 队列来进行维护。初始时将源加入队列。每次从队列中取出一个元素,并对所有与他相邻的 点进行松弛,若某个相邻的点松弛成功,则将其入队,直到队列为空时算法结束。
5.2 算法思想原理
Bellman-Ford 算法能在更普遍的情况下(存在负权边)解决单源点最短路径问题。对于 给定的带权(有向或无向)图 G=(V,E) ,其源点为 s,加权函数 w 是 边集 E 的映射。对 图 G 运行 Bellman-Ford 算法的结果是一个布尔值,表明图中是否存在着一个从源点 s 可达 的负权回路。若不存在这样的回路,算法将给出从源点 s 到 图 G 的任意顶点 v 的最短路径
5.3 算法过程描述
a. 初 始 化 : 将 除 源 点 外 的 所 有 顶 点 的 最 短 距 离 估 计 值
b.迭代求解:反复对边集 E 中的每条边进行松弛操作,使得顶点集 V 中的每个顶点 v 的最短距离估计值逐步逼近其最短距离; (运行|v|-1 次)
c.检验负权回路:判断边集 E 中的每一条边的两个端点是否收敛。如果存在未收敛的顶 点,则算法返回 false,表明问题无解;否则算法返回 true,并且从源点可达的顶点 v 的最短 距离保存在 d[v]中。
5.4 算法适用范围
⑴ 单源最短路径;
⑵ 有向图和无向图;
⑶边权可正可负;
⑷ 差分约束系统。例如 https://cn.vjudge.net/problem/POJ-3159
6.SPFA算法
6.1 算法定义
SPFA(Shortest Path Faster Algorithm)(队列优化)算法是求单源最短路径的一种算法,它还有一个重要的功能是判负环(在差分约束系统中会得以体现),在Bellman-ford算法的基础上加上一个队列优化,减少了冗余的松弛操作,是一种高效的最短路算法。
6.2 算法过程描述
建立一个队列,初始时队列里只有起始点,在建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点去刷新起始点到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。
6.3 算法适用范围
⑴ 单源最短路径;
⑵ 有向图和无向图;
⑶边权可正可负;注:
在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。
几种算法的比较
1)Floyd 算法适用于是一种动态规划算法,稠密图效果最佳,边权可正可负。此算法简单 有效,由于三重循环结构紧凑,对于稠密图,效率要高于执行|V|次 Dijkstra 算法。其优点是 容易理解,可以算出任意两个节点之间的最短距离,代码编写简单 。但是时间复杂度比较 高,不适合计算大量数据。
2)Dijkstra(迪杰斯特拉 )算法是一种按路径长度递增的次序产生最短路径的算法,可求单 源、无负权的最短路。其适用于有向图及无向图,时效性较好,时间复杂度为 O(V*V+E), 可以用优先队列进行优化,优化后时间复杂度变为 0(v*lgn) 。但是由于其遍历计算的节点 很多,所以算法的效率较低。 Bellman-Ford 算法是求解单源最短路问题的一种算法,可以判断有无负权回路(若有, 则不存在最短路) ,时效性较好,时间复杂度 O ( VE ) 。
3)与 Dijkstra 算法不同的是,在 Bellman-Ford 算法中,边的权值可以为负数。设想从我们可以从图中找到一个环路(即从 v 出发,经过若干个点之后又回到 v)且这个环路中所有边的权值之和为负。那么通过这个环 路,环路中任意两点的最短路径就可以无穷小下去。如果不处理这个负环路,程序就会永远 运行下去,而 Bellman-Ford 算法具有分辨这种负环路的能力。
4)很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。它是Bellman-ford的队列优化,它是一种十分高效的最短路算法。SPFA的复杂度大约是O(kE),k是每个点的平均进队次数(一般的,k是一个常数,在稀疏图中小于2)。
总结
Floyd 算法、Dijkstra 算法和 Bellman-Ford 算法是目前的最短路径算法中较为常用的三 个算法。 每种算法都有其自己的特点和优势。 比方说若路径规划问题较为简单, 可采用 Floyd 算法;若路径规划问题涉及到负权边,可采用 Bellman-Ford 算法。总之,在实际应用中, 根据路径问题的特点采用合理的路径规划算法,方能达到最优的效果。