由于单源最短路径算法Dijkstra算法要保证图中没有负权值,所以引出了Bellman-Ford最短路径算法,也是单源最短路径算法,在Bellman-Ford算法中允许存在负权值的路径,且能够检测是否存在负权回路。
算法原理:Bellman-Ford算法首先预测出从起点到各顶点之间最短路径的上限值,然后通过迭代运算方式逐渐减少预测值和实际最短路径之间的误差。Dijkstra算法是基于宽度有限搜索的,每次对某个顶点进行最短路径的计算。Bellman-Ford算法在执行过程中会生成数组upper[],该数组保存各个顶点的最短路径上限,随着算法执行,数组中的数值慢慢减小,算法结束时就变成最短路径。
执行过程:刚开始执行算法,除了起点到起点的最短路径设置为0外,把其他的元素都设置为无穷大。
在这里引入一个概念–松弛操作:
对于图中的每个结点v,我们维持一个属性v.d,用来记录从源结点s到结点v的最短路径权重的上界,称v.d为s到v的最短路估计。
对一条边进行松弛的过程:将从结点s到结点u之间的最短路径距离加上结点u与v之间的边权重,并与当前s到v的最短路估计进行比较,如果前者更小,就对v.d进行更新。每一次松弛操作都对图中所有的边进行松弛。
那么在算法的处理过程中需要进行多少次松弛操作呢?
考虑从任意从源结点s可以到达的结点v,设p=[v0,v1,v2,v3,………,vk]为源节点s到结点v之间的一条最短路径,这里v0=s,vk=v.因为最短路径是简单路径,因此p最多包涵|V|-1条边(|V|是图中所有结点的总条数),故k<=|V|-1.
在这里引入一个定理:
(引理,收敛性质):设G=(V,E)是一个带权重的有向图,权重函数w:E->R。设s为源结点,s—>u->v是图中的一条最短路径,假设图已经进行了一系列的松弛操作,包括对边(u,v)的松弛操作。如果对边(u,v)进行松弛操作之前的任意时刻有u.d=dist(s,u)(即u.d为源结点到u结点最短路径,而不再是最短路径估计),那么在该松弛操作之后的所有时刻v.d=dist(s,v).
证明:
如果在对边(u,v)进行松弛操作前的某时刻有u.d=dist(s,u),则该等式在松弛操作之后也成立。特别的,在对边(u,v)进行松弛后,有v.d<=u.d+w(u,v)=dist(s,u)+w(u,v),又由于最短路径的子路径也是最短路径,即w(u,v)是u到v的最短路径距离,则v.d<=u.d+w(u,v)=dist(s,u)+w(u,v)=dist(s,v),又v.d是最短路径估计,有v.d>=dist(s,v),因此v.d=dist(s,v).
由上面的引理可知,已知u.d=dist(s,u),我们只要一次松弛操作就能够使得v.d=dist(s,v).最短路径最多只有|V|个结点,所以最多只有|V|-1条边,因此我们最多只要|V|-1个松弛操作就能得到最短路径。
(可以这样理解:设图中一定存在最短路径,假设是[v0,v1,v2,v3,………,vk],那么初始只知道v0,第一次松弛操作,肯定可以找到v1,因为v1直接与v0相连接,那么下一次松弛操作会找到v2,。。。)
判断负权回路:
要判断负权回路,只需要将松弛操作次数由|V|-1次改为|V|次。因为如果不存在负权回路那么只要执行|V|-1次松弛操作就能找到最短路径,此时第|V|次操作不改变任何结点处已算出的最短路径距离。相反存在负权回路,那么第|V|次松弛操作至少会改变一个结点的最短路径距离。
c++实现代码:
#include <iostream>
#include <vector>
#define INF 9999999
using namespace std;
vector<int> bellmanFord(int src,vector<pair<int,int>> *adj, int n)
{
vector<int> upper(n,INF);
upper[src]=0;
bool updated;
//松弛操作n次
for(int iter=0; iter<n; ++iter)
{
updated=false;
for(int here=0; here<n; ++here)
{
for(int i=0; i<adj[here].size(); i++)
{
int there=adj[here][i].first;
int cost=adj[here][i].second;
if(upper[there]>upper[here]+cost)
{
upper[there]=upper[here]+cost;
updated=true;
}
}
}
//已经找到最短路径,退出
if(!updated) break;
}
//第n次还发生了最短路径的改变,则存在负权回路
if(updated) upper.clear();
return upper;
}
//有向图
int main()
{
//n:顶点个数,m:边的条数
int n=0,m=0;
cin>>n>>m;
//动态定义数组存储每条边,n代表结点个数,在这里即是边的起始点
vector<pair<int,int>> *adj=new vector<pair<int,int>>[n];
for(int i=0; i<m; i++)
{
int a=0,b=0,c=0;
//a为边的起始点,b为边的终点,c为边上的权值
cin>>a>>b>>c;
//存储的是以a为起点的边
adj[a].push_back(make_pair(b,c));
}
vector<int> result;
//返回的是各结点处的最短路径距离
result=bellmanFord(0,adj,n);
for(int i=0; i<n; i++)
{
cout<<result[i]<<" ";
}
cout<<endl;
}