Bellman-Ford算法—求解带负权边的最短路径
2015-03-25 19:19
417 查看
1.Dijkstra不能得到含有负权边图(这里就不是环路了)的单源最短路径
Dijkstra由于是贪心的,每次都找一个距源点最近的点(dmin),然后将该距离定为这个点到源点的最短路径(d[i]<--dmin);但如果存在负权边,那就有可能先通过并不是距源点最近的一个次优点(dmin'),再通过一个负权边L(L<0),使得路径之和更小(dmin'+L<dmin),则dmin'+L成为最短路径,并不是dmin,这样Dijkstra就被囧掉了。
比如上图:1—>2权值为5,1—>3权值为6,3—>2权值为-2,求1到2的最短路径时,Dijkstra就会贪心的选择权为5的1—>2,但实际上1—>3—>2才是最优的结果,这样Dijkstra算法就无法得到正确的结果。
实际上只要有权重为负权的回路在,就无法得到真正的最短路径,因为只要在负权回路上不断兜圈子,所得的最短路径长度可以任意小。
虽然得不到最短路径,但我们有时需要知道图中是否存在负权回路,而Bellman-Ford算法就可以帮我们解决这个问题。
2、Bellman-Ford算法
定义:如果从结点s到结点v的某条路径上存在权重为负值的环路(必须是环路,不同于负权边),我们定义
,详见CLRS图24-1.
Bellman-Ford算法返回一个布尔值,以表明是否存在一个从源节点可以到达的权重为负值的环路(这样我们就知道这个图是不存在最短路径的了)。
如果存在这样一个环路,算法将告诉我们不存在解决方案;如果没有这种环路存在,算法将给出最短路径和它们的权重。
代码如下:
算法描述:
1,.初始化:将除源点外的所有顶点的最短距离估计值 d[v] ←+∞, d[s] ←0;
2.迭代求解:反复对边集E中的每条边进行松弛操作,使得顶点集V中的每个顶点v的最短距离估计值逐步逼近其最短距离;(运行|v|-1次)
3.检验负权回路:判断边集E中的每一条边的两个端点是否收敛。如果存在未收敛的顶点,则算法返回false,表明问题无解;否则算法返回true,并且从源点可达的顶点v的最短距离保存在 d[v]中。
对于Bellman-Ford算法我们有两个问题需要解决:
问题一:为什么要循环|V| -1次,即为什么算法要对图的每条边都进行了|V| -1 次 松弛操作?
描述性证明:
首先指出,最短路径肯定是个简单路径,不可能包含回路。如果包含回路,且回路的权值和为正的,那么去掉这个回路,可以得到更短的路径;如果回路的权值是负的,那么肯定没有解了。
其次,从源点s可达的所有顶点如果存在最短路径,则这些最短路径构成一个以s为根的最短路径树。Bellman-Ford算法的迭代松弛操作,实际上就是按顶点距离s的层次,逐层生成这棵最短路径树的过程。
在对每条边进行1遍松弛的时候,生成了从s出发,层次至多为1的那些树枝。也就是说,找到了与s至多有1条边相联的那些顶点的最短路径;对每条边进行第2遍松弛的时候,生成了第2层次的树枝,就是说找到了经过2条边相连的那些顶点的最短路径……。图有|V| 个点,又不能有回路,所以最短路径最多|V| -1边(所有的顶点都在最短路径上),因为最短路径最多只包含|v|-1 条边,所以,只需要循环|v|-1 次。
其实说白了,如果我们从源点开始严格按照图固有的层级顺序依次松弛所有的边,那边一次循环下来就可以得到最短路径,而无需循环|v|-1 次,但现在我们不能预先设定每条边松弛的先后顺序,即边的松弛顺序是随机的,我们就只能去做最保险的事情——循环|v|-1 次,这样,无论是以什么样的顺序来松弛所有的边,有一点可以保证就是每次循环至少会有一条边被松弛了,就像上面所说的那样,每次循环至少会把这棵最短路径树向外拓展一层(无论是以怎样“恶劣”的边的次序),那么最远的也就第|v|-1层(一般很少出现这种情况的,所以后面的循环会有大量的松弛操作是浪费的),循环|v|-1
次足以松弛到它。
看一幅图就什么都明白了:
设源点为 s ,
1)如果我们以如下次序来松弛所有的边:( s,t )、( s,y )、( t,x )、( t,y )、( t,z )、( y,x )、( y,z )、( z,x )、( z,s )、( x,t ),即严格的层序顺序,结果一次循环就得到了最短路径;
2)如果我们以如下次序来松弛所有的边:( z,x )、( z,s )、( x,t )、( y,x )、( y,z )、( t,x )、( t,y )、( t,z )、( s,t )、( s,y ),即[u]最恶劣的次序来松弛所有的边,这时就不是一次循环可以解决的了(但至少每次循环会使最短路径树从源点向外扩展一层)。
另外,每实施一次松弛操作,最短路径树上就会有一层顶点达到其最短距离,此后这层顶点的最短距离值就会一直保持不变,不再受后续松弛操作的影响。(但是,每次还要判断松弛,这里浪费了大量的时间,怎么优化?单纯的优化是否可行?)
如果没有负权回路,由于最短路径树的高度最多只能是|v|-1,所以最多经过|v|-1遍松弛操作后,所有从s可达的顶点必将求出最短距离。如果 d[v]仍保持 +∞,则表明从s到v不可达。
如果有负权回路,那么第 |v|-1 遍松弛操作仍然会成功,这时,负权回路上的顶点不会收敛。
问题二:如何保证算法的正确性,即存在负环路时,算法返回false,不存在负环路时,算法返回true?
分两点证明,1)不存在负环路时,都有 v.d < = u.d + w ( u , v )——即 所有的边都松弛到了,这一点我们在问题一已经论证。这时算法返回true,而这显然是成立的;2)存在负环路时,一定存在某条边使得 v.d >u.d + w ( u , v ),此时falg=0,即false. 举例如下
此时,点A的值为-2,点B的值为5,边AB的权重为5,5 > -2 + 5. 检查出来这条边没有收敛。
注1:一定要区分负权边和负权环路!!!
注2:参考资料 Bellman-Ford算法
Dijkstra由于是贪心的,每次都找一个距源点最近的点(dmin),然后将该距离定为这个点到源点的最短路径(d[i]<--dmin);但如果存在负权边,那就有可能先通过并不是距源点最近的一个次优点(dmin'),再通过一个负权边L(L<0),使得路径之和更小(dmin'+L<dmin),则dmin'+L成为最短路径,并不是dmin,这样Dijkstra就被囧掉了。
比如上图:1—>2权值为5,1—>3权值为6,3—>2权值为-2,求1到2的最短路径时,Dijkstra就会贪心的选择权为5的1—>2,但实际上1—>3—>2才是最优的结果,这样Dijkstra算法就无法得到正确的结果。
实际上只要有权重为负权的回路在,就无法得到真正的最短路径,因为只要在负权回路上不断兜圈子,所得的最短路径长度可以任意小。
虽然得不到最短路径,但我们有时需要知道图中是否存在负权回路,而Bellman-Ford算法就可以帮我们解决这个问题。
2、Bellman-Ford算法
定义:如果从结点s到结点v的某条路径上存在权重为负值的环路(必须是环路,不同于负权边),我们定义
,详见CLRS图24-1.
Bellman-Ford算法返回一个布尔值,以表明是否存在一个从源节点可以到达的权重为负值的环路(这样我们就知道这个图是不存在最短路径的了)。
如果存在这样一个环路,算法将告诉我们不存在解决方案;如果没有这种环路存在,算法将给出最短路径和它们的权重。
代码如下:
#include <iostream> using namespace std; const int maxnum = 100; const int maxint = 99999; // 边 typedef struct Edge{ int u, v; // 起点,重点 int weight; // 边的权值 }Edge; Edge edge[maxnum]; // 保存边的值 int dist[maxnum]; // 结点到源点最小距离 int nodenum, edgenum, source; // 结点数,边数,源点 // 初始化图 void init( ) { // 输入结点数,边数,源点 cin >> nodenum >> edgenum >> source; for(int i=1; i<=nodenum; ++i) dist[i] = maxint; dist[source] = 0; for(int i=1; i<=edgenum; ++i) { cin >> edge[i].u >> edge[i].v >> edge[i].weight; if(edge[i].u == source) //注意这里设置初始情况 dist[edge[i].v] = edge[i].weight; } } // 松弛计算 void relax(int u, int v, int weight) { if(dist[v] > dist + weight) dist[v] = dist[u] + weight; } bool Bellman_Ford() { for(int i=1; i<=nodenum-1; ++i) for(int j=1; j<=edgenum; ++j) relax(edge[j].u, edge[j].v, edge[j].weight); bool flag = 1; // 判断是否有负环路 for(int i=1; i<=edgenum; ++i) if(dist[edge[i].v] > dist[edge[i].u] + edge[i].weight) { flag = 0; break; } return flag; } int main() { init( ); if(Bellman_Ford()) //不存在负环路时才能输出最短(从源点到每个顶点都有一个最短路径) for(int i = 1 ;i <= nodenum; i++) //记住这种输出方法! cout << dist[i] << endl; return 0; }
算法描述:
1,.初始化:将除源点外的所有顶点的最短距离估计值 d[v] ←+∞, d[s] ←0;
2.迭代求解:反复对边集E中的每条边进行松弛操作,使得顶点集V中的每个顶点v的最短距离估计值逐步逼近其最短距离;(运行|v|-1次)
3.检验负权回路:判断边集E中的每一条边的两个端点是否收敛。如果存在未收敛的顶点,则算法返回false,表明问题无解;否则算法返回true,并且从源点可达的顶点v的最短距离保存在 d[v]中。
对于Bellman-Ford算法我们有两个问题需要解决:
问题一:为什么要循环|V| -1次,即为什么算法要对图的每条边都进行了|V| -1 次 松弛操作?
描述性证明:
首先指出,最短路径肯定是个简单路径,不可能包含回路。如果包含回路,且回路的权值和为正的,那么去掉这个回路,可以得到更短的路径;如果回路的权值是负的,那么肯定没有解了。
其次,从源点s可达的所有顶点如果存在最短路径,则这些最短路径构成一个以s为根的最短路径树。Bellman-Ford算法的迭代松弛操作,实际上就是按顶点距离s的层次,逐层生成这棵最短路径树的过程。
在对每条边进行1遍松弛的时候,生成了从s出发,层次至多为1的那些树枝。也就是说,找到了与s至多有1条边相联的那些顶点的最短路径;对每条边进行第2遍松弛的时候,生成了第2层次的树枝,就是说找到了经过2条边相连的那些顶点的最短路径……。图有|V| 个点,又不能有回路,所以最短路径最多|V| -1边(所有的顶点都在最短路径上),因为最短路径最多只包含|v|-1 条边,所以,只需要循环|v|-1 次。
其实说白了,如果我们从源点开始严格按照图固有的层级顺序依次松弛所有的边,那边一次循环下来就可以得到最短路径,而无需循环|v|-1 次,但现在我们不能预先设定每条边松弛的先后顺序,即边的松弛顺序是随机的,我们就只能去做最保险的事情——循环|v|-1 次,这样,无论是以什么样的顺序来松弛所有的边,有一点可以保证就是每次循环至少会有一条边被松弛了,就像上面所说的那样,每次循环至少会把这棵最短路径树向外拓展一层(无论是以怎样“恶劣”的边的次序),那么最远的也就第|v|-1层(一般很少出现这种情况的,所以后面的循环会有大量的松弛操作是浪费的),循环|v|-1
次足以松弛到它。
看一幅图就什么都明白了:
设源点为 s ,
1)如果我们以如下次序来松弛所有的边:( s,t )、( s,y )、( t,x )、( t,y )、( t,z )、( y,x )、( y,z )、( z,x )、( z,s )、( x,t ),即严格的层序顺序,结果一次循环就得到了最短路径;
2)如果我们以如下次序来松弛所有的边:( z,x )、( z,s )、( x,t )、( y,x )、( y,z )、( t,x )、( t,y )、( t,z )、( s,t )、( s,y ),即[u]最恶劣的次序来松弛所有的边,这时就不是一次循环可以解决的了(但至少每次循环会使最短路径树从源点向外扩展一层)。
另外,每实施一次松弛操作,最短路径树上就会有一层顶点达到其最短距离,此后这层顶点的最短距离值就会一直保持不变,不再受后续松弛操作的影响。(但是,每次还要判断松弛,这里浪费了大量的时间,怎么优化?单纯的优化是否可行?)
如果没有负权回路,由于最短路径树的高度最多只能是|v|-1,所以最多经过|v|-1遍松弛操作后,所有从s可达的顶点必将求出最短距离。如果 d[v]仍保持 +∞,则表明从s到v不可达。
如果有负权回路,那么第 |v|-1 遍松弛操作仍然会成功,这时,负权回路上的顶点不会收敛。
问题二:如何保证算法的正确性,即存在负环路时,算法返回false,不存在负环路时,算法返回true?
分两点证明,1)不存在负环路时,都有 v.d < = u.d + w ( u , v )——即 所有的边都松弛到了,这一点我们在问题一已经论证。这时算法返回true,而这显然是成立的;2)存在负环路时,一定存在某条边使得 v.d >u.d + w ( u , v ),此时falg=0,即false. 举例如下
此时,点A的值为-2,点B的值为5,边AB的权重为5,5 > -2 + 5. 检查出来这条边没有收敛。
注1:一定要区分负权边和负权环路!!!
注2:参考资料 Bellman-Ford算法
相关文章推荐
- 图的最短路径算法(四)--Bellman-Ford(解决负权边)单源点最短路径
- 求解最短路径Bellman_Ford 算法优化版——结合队列
- 图论;单源最短路径;拓扑排序+松弛(有向无回路);Bellman-Ford(回路,负权回路);Dijkstra(无负权,可回路);可以用最小堆实现算法的优化;
- 图论;单源最短路径;拓扑排序+松弛(有向无回路);Bellman-Ford(回路,负权回路)Dijkstra(无负权,可回路);可以用最小堆实现算法的优化;
- Bellman-ford算法求解单源点最短路径初始版本
- 图论;单源最短路径;拓扑排序+松弛(有向无回路);Bellman-Ford(回路,负权回路);Dijkstra(无负权,可回路);可以用最小堆实现算法的优化;
- 最短路径算法—Bellman-Ford(贝尔曼-福特)算法分析与实现(C/C++)
- 几个最短路径算法Floyd、Dijkstra、Bellman-Ford、SPFA的比较
- 单源最短路径 : Bellman-Ford 算法
- 【经典算法】Bellman-Ford最短路径算法
- 最短路径问题--Bellman-Ford最短路径算法
- 几个最短路径算法Floyd、Dijkstra、Bellman-Ford、SPFA的比较
- 多路径路由算法选择(7)——最短路径算法Dijkstra,Bellman-Ford,Floyd-Warshall,Johnson
- 最短路径算法比较(Dijkstra、Bellman-Ford、SPFA)及实现(Java)
- 【算法】最短路径之Bellman-Ford
- Bellman_Ford算法求最短路径
- 最短路径算法----Bellman-ford和SPFA算法
- 最短路径算法—Bellman-Ford(贝尔曼-福特)算法分析与实现(C/C++)
- POJ 1860 Currency Exchange Bellman-Ford算法求单源最短路径并判断是否有正权回路
- 几个最短路径算法Floyd、Dijkstra、Bellman-Ford、SPFA的比较