最短路径算法与应用中的问题分析(史上最全路径算法总结)
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
5 V2 V0 V1 7 -5
二,任意权值的单源最短路径算法,解决上述问题 2.
1, 问题的描述: 给定一个有向带权图 D 与源点 v,各边上的权值为任意实数,要求找出从 v 出 发到 D 中其它各顶点的最短路径。 2, 算法的主要思想: 此种情况下我们可以用 Bellman-ford 算法。 当图中没有由带负权值的边组成的回 路时,有 n 个顶点的图中任意两个顶点之间如果存在最短路径,此路径最多有 n-1 条边。 Bellman-Ford 方法构造一个最短路径长度数组序列 dist1[u], dist2[u], …, distn-1[u],其中,dist n-1[u]是从源点 v 出发最多经过不构成带负长度边回路的 n-1 条边到达终点 u 的最短路径长度。算法的最终目的是计算出 dist
六,如果权值非负,求其总长最短的一条过全部节点的初级回路。解 决问题 7。
1,问题的描述: 给定一个正权完全图, 求其总长最短的哈密顿回路。 所谓的哈密顿回路便是无向 图中一条经过全部节点的初级回路。这个便是图论中非常经典的旅行商问题。 2,算法的主要思想: 解决旅行商问题的一种比较精确的求解方法是分支与界法。 分支与界法的基本思路是: 1, 首先将边权由小到大排序,初始界 d0 。 2, 在边权序列中依次选边进行深探,直到选取 n 条边,判断是否构成 H 回路, 若是, d0 d (s1) ,结束。 3, 继续深探, 依次删除当前 si 中的最长边, 加入后面第一条待选边, 进行深探, 如果它是 H 回路且 d( si ) d 0 ,则 d0 d ( si ) 作为界。 4, 退栈过程,不能再深探时需要退栈。如果栈空,结束,其最佳值为 d0。否则 如果新分支的 d( si ) d 0 ,继续退栈;若 d(si)<d0,转 3. 这种搜索过程是在不断的构造分支与确定界值。一旦确定了界值,则对大于等于 界值的分支不在搜索, 而且最后得到的界值就是问题的最佳解。但是在最坏的情 况下,该算法的时间复杂度是 O(n!)。因此在实际问题中,我们经常采用近似算 法求解问题的近似最优解,近似算法中比较好的是“便宜”算法。 便宜算法的基本思路: 初始化时 T=(1,1); S ={2,3, · · · ,n} T 是一个不断扩充的初级回路,最初是一个自环。首先我们选取 S 中与 T 距离最 近的节点 j。设(j,t)是相应的边,这时节点 j 或插入到回路 T 中 t 的前面或者 插入到其后面,这根据 j 插入后回路 T 长度增量的大小而定。即如果 ,则插入到 t 与 t1 之间,否则 w ( j ,t ) w ( j ,t 1) w( t ,t 1) w (j t , ) w (j t , 2 ) w t ( t, 2 ) 插入在 t 与 t2 之间。
将新选出的边从 E 中剔除:E = E-{(u, v)}; } if (Vmst 包含的顶点少于 n) cout << "不是最小生成树" << endl;
4, 算法在应用过程中的问题分析: Prim 算法适用于边稠密的网络。Kruskal 算法更适合于边稀疏的情形。当各边有 相同权值时, 由于选择的随意性, 产生的生成树可能不唯一。 从二者的原理来看, Kruskal 算法是基于边的算法,而 Prim 算法则是基于顶点的。因此对于一个边数 很多的图, 用 Kruskal 算法求解并不明智。而顶点与边数之比相对较大的图用 Kruskal 算法则效率会更高。 最小生成树是解决 n 个节点之间相互连通的重要算法,n 个节点相互连通在构建 合理的城市交通网络,高效的通信网络等问题方面有重要应用。
S {v0}; dist[ j ] Edge[0][ j ]
//dist[j]为 v0 到节点 vj 的距离。
②求最短路径的长度:
dist[k ] min{dist[i]}, i V S S S {k};
③修改: dist[i ] min{dist[i], dist[k ] Edge[k ][i ]}, 对于每一个i V S ④判断:若 S=V,则算法结束,否则转② 算法的复杂度是 O(n^2). 4,算法应用中的问题分析: 虽然在讨论的过程中我们构造的是有向图,但是 D 算法对于无向图依然适用。 这个算法的不足是如果有向图中出现负权值,那么计算结果会出现错误,如图 1 所示,如果按照 D 算法 v0 到 v2 的最短路径应该是 5,而实际上 v0 到 v2 的最短 路 径 应 该 是 7-5=2 。 在 有 负 权 值 出 现 时 , 我 们 应 该 用 一 种 全 新 的 算 法 ---Bellman-ford 算法。
template <class T, class E> void Bellman-Ford (Graph<T, E>& G, int v, E dist[], int path[]) { //在有向带权图中有的边具有负的权值。从顶点 v //找到所有其他顶点的最短路径。 E w; int i, k, u, n = G.NumberOfVertices(); //计算 dist1[i]
五,n 个节点的连通图之间的最短路径,解决上述问题 6.
1, 问题的描述: 在规划建立 n 个城市之间的通讯网络时,至少要架设 n-1 条线路,如果在任何两 个城市间建造通讯线路的成本已经确定, 那么如何使总造价最低?对于这类问题 便是最小生成树问题,他要找的便是连通 n 个节点的代价最小的生成树。 2, 算法的主要思想: 解决最小生成树有两种算法,Kruskal 算法和 Prim 算法 Kruskal 算法是:
几种最短路径算法与应用中的问题分析
对于一个带权图,求解满足以下条件的最短路径:
1, 如果权值全部为非负,求一个节点到其它各个节点的最短路径。 2, 如果权值有正有负,求一个节点到其它各个节点的最短路径。 3, 如果权值全部为非负,求任意两个节点间的最短路径。 4, 如果权值有正有负,求任意两个节点间的最短路径。 5, 不考虑边上的权值,求一个节点到其它各个节点所需经过的最少节点数。 6, 如果权值非负,求使 n 个节点连通的最小支撑树的最小花费。 7, 如果权值非负,求其总长最短的一条过全部节点的初级回路。 8, 对于无向图,如果权值非负,求从某节点出发经过每条边至少一次最后回到 出发点的最短回路 9,实际应用中应用广泛的 A*算法 上面的 9 个问题基本包含了各种形式和情况下的最短路径。下面我将对上面的 9 个问题一一讨论。
从连通网络 N = {V, E}中的某一顶点 u0 出发, 选择与它关联的具有最小权值的边 (u0, v), 将其顶点加入到生成树顶点集合 U 中。以后每一步从一个顶点在集合 U 中, 而另一个顶点 不在集合 U 中的各条边中选择权值最小的边(u, v), 把它的顶点加入到集合 U 中。如此继续 下去, 直到网络中的所有顶点都加入到生成树顶点集合 U 中为止。
n-1
[u]。用递推
公式 distk [u] = min { distk-1 [u], min { distk-1 [j]+Edge[j][u] } }计算 distk [u],可得从 源点 v 绕过各顶点 j,最多经过不构成带负长度边回路的 k 条边到达终点 u 的最 短路径长度。用它与 distk-1 [u]比较,取小者作为 distk [u]的值,依次类推,直到 从顶点 v 到其它各顶点的最短路径全部求出为止。 3, 算法的实现:
一,非负权值的单源最短路径,解决上述问题 1.
1,问题的描述: 给定一个有向带权图 D 与源点 v,各边上的权值均为非负数,要求找出从 v 出发 到 D 中其它各顶点的最短路径。 对于这样的问题,我们可以运用 Dijkstra 算法。, 2,算法的主要思想是: 利用路径长度的递增次序, 逐步产生最短路径。首先求出长度最短的一条最短路 径,然后参照它求出长度次短的一条最短路径,依次类推,直到从顶点 v 到其它 各顶点的最短路径全部求出为止。 3,Dijsktra 算法的伪代码描述是: ①初始化:
for (i = 0; i < n; i++) { dist[i] = G.getWeight(v, i);
if (i != v && dist[i] < maxValue) path[i] = v; else path[i] = -1; } for (k = 2; k < n; k++) for (u = 0; u < n; u++) if (u != v) for (i = 0; i < n; i++) { w = G.getWeight(i, u); if (w > 0 && w < maxValue && dist[u] > dist[i]+w) { dist[u] = dist[i]+w; path[u] = i; } } }; 4, 算法在应用过程中的问题分析: 虽然该算法是对于有向图进行讨论的,但是对于无向图同样适用。 但是对于图中包含有由带负权值的边组成的回路时,如图 2 所示,如果来回 在回路中转圈,则路径的长度会越来越小,从顶点 0 到顶点 2 的最短路径长 度可达到 - 。显然此时 Bellman-ford 算法不适用。 //计算 dist2[i]到 distn-1[i]
①初始化 A(-1) [i][j] = Edge[i][j]; ②对于每一对(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 ③判断:若全部的 i 与 j 已经遍历完全,则算法结束,否则转② 4, 算法在应用过程中的问题分析
3, 算法的伪代码: Kruskal 算法的伪代码是: T = ; //T 是最小生成树的边集合,E 是带权无向图的边集合
while ( T 包含的边少于 n-1 && E 不空) { 从 E 中选一条具有最小代价的边 (v, w); 从 E 中删去(v, w); 如果(v, w)加到 T 中后不会产生回路, 则将 (v, w)加入 T; 否则放弃(v, w); } if ( T 中包含的边少于 n-1 条) cout << “找不到最小生成树" << endl; Prim 算法的伪代码是: 选定构造最小生成树的出发顶点 u0; Vmst = {u0},Emst = ; while (Vmst 包含的顶点少于 n && E 不空) { 从 E 中选一条边(u, v), uVmst∩vV-Vmst, 且具有最小代价(cost); 令 Vmst = Vmst∪{v}, Emst = Emst∪{(u, v)};
Floyd 算法的时间复杂度是 O(n3),这和改进 D 算法的时间复杂度是相同的,但 是 Floyd 算法在有负权值时依然适用,这一点要优于 D 算法。
四, 不考虑边上的权值, 求一个节点到其它各个节点所需经过的最少 节点数,解决上述问题 5
这个问题比较简单,我们只需要变通一下,套用一下 D 算法,可以将边上的权 值设为 1,则 D 算法求出的最短路径即是我们需要的经过最少节点数的路径。
三,所有顶点之间的最短路径,解决上述问题 3 和 4.
1,问题的描述: 已知一个各边权值均大于 0 的有向带权图,对于每一对节点 Vi Vj ,要求出 Vi 到
Vj 之间的最短路径与最短路Байду номын сангаас长度
解决这种问题,如果我们利用先前的 D 算法,轮流以每一个顶点为源点,重复 执行 Dijkstra 算法 n 次,就可以得到每一对节点之间的最短路径,此时的算法时 间复杂度是 O(n3 ) ,但是这种算法虽然套用比较直接,但是和 D 算法一样,有向 图中不能出现负权值。 而对于这种问题,还有一种算法 Floyd 算法。 2, 算法的主要思想: Floyd 算法仍然使用图的邻接矩阵 Edge[n][n] 来存储有向带权图。首先初始化 Edge[n][n]矩阵,然后逐步尝试在原路径中加入其他顶点作为中间节点,如果增 加中间节点后, 得到的路径比原来的路径长度减小了, 则以此新路径代替原路径, 修改矩阵元素,代入新的更短的路径长度,以此类推,这样的增加中间节点,可 以得到 Floyd 算法。 3, 算法的伪代码: 定义一个 n 阶方阵序列:A(-1), A(0), …, A(n-1).
设一个有 n 个顶点的连通网络 N = { V, E }, 最初先构造一个只有 n 个顶点, 没有边的非 连通图 T = { V, }, 图中每个顶点自成一个连通分量。当在 E 中选到一条具有最小
权值的边时, 若该边的两个顶点落在不同的连通分量上,则将此边加入到 T 中; 否则将此边舍去,重新选择一条权值最小的边。如此重复下去, 直到所有顶点在 同一个连通分量上为止。 本质是: 在保证无回路前提下选 n-1 条具有最小权值的 边。 Prim 算法是:
二,任意权值的单源最短路径算法,解决上述问题 2.
1, 问题的描述: 给定一个有向带权图 D 与源点 v,各边上的权值为任意实数,要求找出从 v 出 发到 D 中其它各顶点的最短路径。 2, 算法的主要思想: 此种情况下我们可以用 Bellman-ford 算法。 当图中没有由带负权值的边组成的回 路时,有 n 个顶点的图中任意两个顶点之间如果存在最短路径,此路径最多有 n-1 条边。 Bellman-Ford 方法构造一个最短路径长度数组序列 dist1[u], dist2[u], …, distn-1[u],其中,dist n-1[u]是从源点 v 出发最多经过不构成带负长度边回路的 n-1 条边到达终点 u 的最短路径长度。算法的最终目的是计算出 dist
六,如果权值非负,求其总长最短的一条过全部节点的初级回路。解 决问题 7。
1,问题的描述: 给定一个正权完全图, 求其总长最短的哈密顿回路。 所谓的哈密顿回路便是无向 图中一条经过全部节点的初级回路。这个便是图论中非常经典的旅行商问题。 2,算法的主要思想: 解决旅行商问题的一种比较精确的求解方法是分支与界法。 分支与界法的基本思路是: 1, 首先将边权由小到大排序,初始界 d0 。 2, 在边权序列中依次选边进行深探,直到选取 n 条边,判断是否构成 H 回路, 若是, d0 d (s1) ,结束。 3, 继续深探, 依次删除当前 si 中的最长边, 加入后面第一条待选边, 进行深探, 如果它是 H 回路且 d( si ) d 0 ,则 d0 d ( si ) 作为界。 4, 退栈过程,不能再深探时需要退栈。如果栈空,结束,其最佳值为 d0。否则 如果新分支的 d( si ) d 0 ,继续退栈;若 d(si)<d0,转 3. 这种搜索过程是在不断的构造分支与确定界值。一旦确定了界值,则对大于等于 界值的分支不在搜索, 而且最后得到的界值就是问题的最佳解。但是在最坏的情 况下,该算法的时间复杂度是 O(n!)。因此在实际问题中,我们经常采用近似算 法求解问题的近似最优解,近似算法中比较好的是“便宜”算法。 便宜算法的基本思路: 初始化时 T=(1,1); S ={2,3, · · · ,n} T 是一个不断扩充的初级回路,最初是一个自环。首先我们选取 S 中与 T 距离最 近的节点 j。设(j,t)是相应的边,这时节点 j 或插入到回路 T 中 t 的前面或者 插入到其后面,这根据 j 插入后回路 T 长度增量的大小而定。即如果 ,则插入到 t 与 t1 之间,否则 w ( j ,t ) w ( j ,t 1) w( t ,t 1) w (j t , ) w (j t , 2 ) w t ( t, 2 ) 插入在 t 与 t2 之间。
将新选出的边从 E 中剔除:E = E-{(u, v)}; } if (Vmst 包含的顶点少于 n) cout << "不是最小生成树" << endl;
4, 算法在应用过程中的问题分析: Prim 算法适用于边稠密的网络。Kruskal 算法更适合于边稀疏的情形。当各边有 相同权值时, 由于选择的随意性, 产生的生成树可能不唯一。 从二者的原理来看, Kruskal 算法是基于边的算法,而 Prim 算法则是基于顶点的。因此对于一个边数 很多的图, 用 Kruskal 算法求解并不明智。而顶点与边数之比相对较大的图用 Kruskal 算法则效率会更高。 最小生成树是解决 n 个节点之间相互连通的重要算法,n 个节点相互连通在构建 合理的城市交通网络,高效的通信网络等问题方面有重要应用。
S {v0}; dist[ j ] Edge[0][ j ]
//dist[j]为 v0 到节点 vj 的距离。
②求最短路径的长度:
dist[k ] min{dist[i]}, i V S S S {k};
③修改: dist[i ] min{dist[i], dist[k ] Edge[k ][i ]}, 对于每一个i V S ④判断:若 S=V,则算法结束,否则转② 算法的复杂度是 O(n^2). 4,算法应用中的问题分析: 虽然在讨论的过程中我们构造的是有向图,但是 D 算法对于无向图依然适用。 这个算法的不足是如果有向图中出现负权值,那么计算结果会出现错误,如图 1 所示,如果按照 D 算法 v0 到 v2 的最短路径应该是 5,而实际上 v0 到 v2 的最短 路 径 应 该 是 7-5=2 。 在 有 负 权 值 出 现 时 , 我 们 应 该 用 一 种 全 新 的 算 法 ---Bellman-ford 算法。
template <class T, class E> void Bellman-Ford (Graph<T, E>& G, int v, E dist[], int path[]) { //在有向带权图中有的边具有负的权值。从顶点 v //找到所有其他顶点的最短路径。 E w; int i, k, u, n = G.NumberOfVertices(); //计算 dist1[i]
五,n 个节点的连通图之间的最短路径,解决上述问题 6.
1, 问题的描述: 在规划建立 n 个城市之间的通讯网络时,至少要架设 n-1 条线路,如果在任何两 个城市间建造通讯线路的成本已经确定, 那么如何使总造价最低?对于这类问题 便是最小生成树问题,他要找的便是连通 n 个节点的代价最小的生成树。 2, 算法的主要思想: 解决最小生成树有两种算法,Kruskal 算法和 Prim 算法 Kruskal 算法是:
几种最短路径算法与应用中的问题分析
对于一个带权图,求解满足以下条件的最短路径:
1, 如果权值全部为非负,求一个节点到其它各个节点的最短路径。 2, 如果权值有正有负,求一个节点到其它各个节点的最短路径。 3, 如果权值全部为非负,求任意两个节点间的最短路径。 4, 如果权值有正有负,求任意两个节点间的最短路径。 5, 不考虑边上的权值,求一个节点到其它各个节点所需经过的最少节点数。 6, 如果权值非负,求使 n 个节点连通的最小支撑树的最小花费。 7, 如果权值非负,求其总长最短的一条过全部节点的初级回路。 8, 对于无向图,如果权值非负,求从某节点出发经过每条边至少一次最后回到 出发点的最短回路 9,实际应用中应用广泛的 A*算法 上面的 9 个问题基本包含了各种形式和情况下的最短路径。下面我将对上面的 9 个问题一一讨论。
从连通网络 N = {V, E}中的某一顶点 u0 出发, 选择与它关联的具有最小权值的边 (u0, v), 将其顶点加入到生成树顶点集合 U 中。以后每一步从一个顶点在集合 U 中, 而另一个顶点 不在集合 U 中的各条边中选择权值最小的边(u, v), 把它的顶点加入到集合 U 中。如此继续 下去, 直到网络中的所有顶点都加入到生成树顶点集合 U 中为止。
n-1
[u]。用递推
公式 distk [u] = min { distk-1 [u], min { distk-1 [j]+Edge[j][u] } }计算 distk [u],可得从 源点 v 绕过各顶点 j,最多经过不构成带负长度边回路的 k 条边到达终点 u 的最 短路径长度。用它与 distk-1 [u]比较,取小者作为 distk [u]的值,依次类推,直到 从顶点 v 到其它各顶点的最短路径全部求出为止。 3, 算法的实现:
一,非负权值的单源最短路径,解决上述问题 1.
1,问题的描述: 给定一个有向带权图 D 与源点 v,各边上的权值均为非负数,要求找出从 v 出发 到 D 中其它各顶点的最短路径。 对于这样的问题,我们可以运用 Dijkstra 算法。, 2,算法的主要思想是: 利用路径长度的递增次序, 逐步产生最短路径。首先求出长度最短的一条最短路 径,然后参照它求出长度次短的一条最短路径,依次类推,直到从顶点 v 到其它 各顶点的最短路径全部求出为止。 3,Dijsktra 算法的伪代码描述是: ①初始化:
for (i = 0; i < n; i++) { dist[i] = G.getWeight(v, i);
if (i != v && dist[i] < maxValue) path[i] = v; else path[i] = -1; } for (k = 2; k < n; k++) for (u = 0; u < n; u++) if (u != v) for (i = 0; i < n; i++) { w = G.getWeight(i, u); if (w > 0 && w < maxValue && dist[u] > dist[i]+w) { dist[u] = dist[i]+w; path[u] = i; } } }; 4, 算法在应用过程中的问题分析: 虽然该算法是对于有向图进行讨论的,但是对于无向图同样适用。 但是对于图中包含有由带负权值的边组成的回路时,如图 2 所示,如果来回 在回路中转圈,则路径的长度会越来越小,从顶点 0 到顶点 2 的最短路径长 度可达到 - 。显然此时 Bellman-ford 算法不适用。 //计算 dist2[i]到 distn-1[i]
①初始化 A(-1) [i][j] = Edge[i][j]; ②对于每一对(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 ③判断:若全部的 i 与 j 已经遍历完全,则算法结束,否则转② 4, 算法在应用过程中的问题分析
3, 算法的伪代码: Kruskal 算法的伪代码是: T = ; //T 是最小生成树的边集合,E 是带权无向图的边集合
while ( T 包含的边少于 n-1 && E 不空) { 从 E 中选一条具有最小代价的边 (v, w); 从 E 中删去(v, w); 如果(v, w)加到 T 中后不会产生回路, 则将 (v, w)加入 T; 否则放弃(v, w); } if ( T 中包含的边少于 n-1 条) cout << “找不到最小生成树" << endl; Prim 算法的伪代码是: 选定构造最小生成树的出发顶点 u0; Vmst = {u0},Emst = ; while (Vmst 包含的顶点少于 n && E 不空) { 从 E 中选一条边(u, v), uVmst∩vV-Vmst, 且具有最小代价(cost); 令 Vmst = Vmst∪{v}, Emst = Emst∪{(u, v)};
Floyd 算法的时间复杂度是 O(n3),这和改进 D 算法的时间复杂度是相同的,但 是 Floyd 算法在有负权值时依然适用,这一点要优于 D 算法。
四, 不考虑边上的权值, 求一个节点到其它各个节点所需经过的最少 节点数,解决上述问题 5
这个问题比较简单,我们只需要变通一下,套用一下 D 算法,可以将边上的权 值设为 1,则 D 算法求出的最短路径即是我们需要的经过最少节点数的路径。
三,所有顶点之间的最短路径,解决上述问题 3 和 4.
1,问题的描述: 已知一个各边权值均大于 0 的有向带权图,对于每一对节点 Vi Vj ,要求出 Vi 到
Vj 之间的最短路径与最短路Байду номын сангаас长度
解决这种问题,如果我们利用先前的 D 算法,轮流以每一个顶点为源点,重复 执行 Dijkstra 算法 n 次,就可以得到每一对节点之间的最短路径,此时的算法时 间复杂度是 O(n3 ) ,但是这种算法虽然套用比较直接,但是和 D 算法一样,有向 图中不能出现负权值。 而对于这种问题,还有一种算法 Floyd 算法。 2, 算法的主要思想: Floyd 算法仍然使用图的邻接矩阵 Edge[n][n] 来存储有向带权图。首先初始化 Edge[n][n]矩阵,然后逐步尝试在原路径中加入其他顶点作为中间节点,如果增 加中间节点后, 得到的路径比原来的路径长度减小了, 则以此新路径代替原路径, 修改矩阵元素,代入新的更短的路径长度,以此类推,这样的增加中间节点,可 以得到 Floyd 算法。 3, 算法的伪代码: 定义一个 n 阶方阵序列:A(-1), A(0), …, A(n-1).
设一个有 n 个顶点的连通网络 N = { V, E }, 最初先构造一个只有 n 个顶点, 没有边的非 连通图 T = { V, }, 图中每个顶点自成一个连通分量。当在 E 中选到一条具有最小
权值的边时, 若该边的两个顶点落在不同的连通分量上,则将此边加入到 T 中; 否则将此边舍去,重新选择一条权值最小的边。如此重复下去, 直到所有顶点在 同一个连通分量上为止。 本质是: 在保证无回路前提下选 n-1 条具有最小权值的 边。 Prim 算法是: