昨天比赛的时候碰到了带负权的单源最短路径的题目,没做出来,Dijkstra不可以求带负权的最短路。学习了一下Bellman-ford算法。
Bellman-ford算法可以在O(VE) 的时间内求出带负权的单源最短路(除存在负权回路),而且可以判断图中是否存在负权回路。
我觉的Bellman-ford算法的关键点在于在一个|v |个点的图中,任意两个点之间的最短路之间最多有|v|-1条边,这样把每条边松弛|v|-1次后就是其最短距离,该算法就是这样不断松弛逐渐逼近最短距离。
判断是否存在带负权的回路:
因为每条边经过|v|-1次松弛后就是其最短距离了,如果说还有边可以松弛,那就该图中一定存在带负权的回路。否则不存在。
int Bellman_ford()
{
for(int i=0;i<n;i++) // 初始化,距离赋值为无穷大,Dist[i]表示i点到源点最短距离
Dist[i]=Inf;
Dist[start]=0;
for(int i=0;i<n-1;i++) //对每条边松弛|v-1|次,这里采用边集数组的存储方式
{
for(int k=0;k<cunt;k++)
{
if(Dist[E[k].v]>Dist[E[k].u]+E[k].cost) // 松弛判断
Dist[E[k].v]=Dist[E[k].u]+E[k].cost;
}
}
for(int i=0;i<cunt;i++)
{
if(Dist[E[i].v]>Dist[E[i].u]+E[i].cost) //判断是否存在负权回路,flag为0表示存在负权回路
{
flag=0;
break;
}
}
}
Bellman-ford算法的改进优化—SPFA算法:
Bellman-ford可以利用队列进行优化,也就是SPFA算法:
SPFA对Bellman-Ford算法优化的关键之处在于意识到:只有那些在前一遍松弛中改变了距离估计值的点,才可能引起他们的邻接点的距离估计值的改变。因此,算法大致流程是用一个队列来进行维护,即用一个先进先出的队列来存放被成功松弛的顶点。初始时,源点s入队。当队列不为空时,取出队首顶点,对它的邻接点进行松弛。如果某个邻接点松弛成功,且该邻接点不在队列中,则将其入队。经过有限次的松弛操作后,队列将为空,算法结束。SPFA算法的实现,需要用到一个先进先出的队列 queue 和一个指示顶点是否在队列中的标记数组InQ。为了方便查找某个顶点的邻接点,图采用临界表存储。在SPFA中判断存在负权回路的方法是如果一个点进队次数大于|v|次,则表示存在负权回路,所以用一个数组f表示进对次数。
代码:
void Spfa()
{
queue<int > q;
q.push(start); //源点进队
for(int i=0;i<n;i++) // 初始化
{
InQ[i]=false;
f[i]=0;
Dist[i]=Inf;
}
Dist[start]=0;flag=1;
while(!q.empty())
{
int temp=q.front();
q.pop();
InQ[temp]=false;
if(f[temp]>n)
{
flag=0;
break;
}
for(int i=0;i<adj[temp].size();i++)
{
int v=adj[temp][i];
if(Dist[v]>Dist[temp]+cost[temp][v]) //松弛
{
if(!InQ[v])
{
q.push(v);
InQ[v]=true;
f[v]++;
}
Dist[v]=Dist[temp]+cost[temp][v];
}
}
}
}