图和树(中)SPFA——有负边的单源点最短路

本文深入探讨了SPFA(Shortest Path Faster Algorithm)和Bellman-Ford算法在处理包含负权边的图中寻找单源最短路径的问题。通过分析两种算法的松弛过程,指出它们如何解决负权环路的挑战。SPFA通过优化队列避免重复松弛,平均时间复杂度为O(km),而Bellman-Ford在n-1轮松弛后仍能发现负权环路。文章还提供了两种算法的实现代码,帮助读者理解算法细节。
摘要由CSDN通过智能技术生成

SPFA求有负边的单源点最短路

Bellman-ford算法

最短路经过的路径条数,小于图中点的个数
若大于等于点的个数,则意味着存在某点被重复经过

当松弛边(u,v)时,如果dis[u]已经时最短路且u在v的最短路上,则松弛结束后dis[v]也是最短路,并且从此以后其dis的值不会发生变化。

对图中每一条边都进行松弛,由于最短路的路径条数小于点的个数,故松弛 点数-1轮即可

第i轮松弛完毕后,所有经过i跳变的最短路都被确定
(以上我也没懂)

for(int i = 1;i <= n;i++)
{
 dis[i] = INF;
 pre[i] = 0;
}
dis[s] = 0;
for(int k = 1;k<=n;k++)
   for(int i = 1;i <= m;i++)
     if(dis[v[i]] > dis[u[i]] + w[i])
     {
       dis[v[i]] = dis[u[i]] + w;
       pre[v[i]] = u[i];
     }

时间复杂度O(nm)

SPFA shortest path faster algorithm

观察Bellman-ford算法的松弛过程,松弛擦做仅仅发生在最短路径前导结点中的已经成功松弛过的节点上。
第一轮,与s临街的点被松弛–>最短路径上的第一条边
第二轮与第一轮被松弛的相邻接的点被松弛->最端路径上的第二条边

只做有效的松弛操作:
建一个队列
队列中存储被成功松弛的点
每次从队首取店并松弛其邻接点
如果邻接点松弛成功就将其加入队列

思考:
存在重复入队问题, 数组计录是否已经在队列中

void spfa(int s )
{
   for(int i = 1;i <= n;i++) cna[i] = 0,dis[i] = inf;
   dis[s] = 0;
   can[s] = 1;
   queue<int> p;
   p.push(s);
   while(!p.empty())
   {
      int now = p.front();
      p.pop();
       for(int i = point[now];i;i=nxt[i])
         if(dis[v[i]] > dis[now] + len[i])
         {
           dis[v[i]] = dis[now] + len[i];
           pre[v[i]] = now;
           if(!can[v[i]])//防止重复入队
           {
             can[v[i]] = 1;
             p.push(v[i]);
           }
         }  
       can[now] = 0;//出队标为0
   }
}

时间复杂度,平均为o(km)
k是一个小于n的小常数

负权环路

Bellman-ford算法及其队列优化可以解决负权边的问题,而且SPFA的时间复杂度较为优秀。

S不可达,存在负权环路的时候,最短路是不存在的。
如果有负权环路,那么每走一圈负权环路,最短路长度都会减小,无限循环则会无线小下去,因此时不存在最短路的。
需要判断途中是否含有负环
如果存在负环,那么最短录经过的边数会打与等于n
一些边被松弛的次数会大于等于n
如果在地n批次松弛操作时还存在能够成功被松弛的边做说明图中存在负环

 //修改后的Bellman-ford算法
// 初始化
for(int i = 1;i <=n;i++)
{ 
  dis[i] = inf; //#define inf 1e9
   pre[i] = 0; 
}
dis[s] = 0;
for(int k = 1;k < n;k++)
  for(int i = 1;i <= m;i++)
    if(dis[v[i]] > dis[u[i]] + w[i])
    {
      dis[v[i]] = dis[u[i]] + w;
      pre[v[i]] = u[i];
    }
  
//在进行n-1轮松弛之后,再进行一次松弛,如果有边能够被松弛成功,则说明存在负环
for(int ii = 1;i <=m;i++)
  if(dis[v[i]] > dis[u[i]] + w[i])
  { 
   //存在负环
  }

修改后的SPFA算法
cnt[x]表示到点x当前最短路上的边数
每次更新最短路时更新cnt[v[,如果某一时刻cnt[v]大于等于n的话,说明图中存在负环

void spfa(int s)
{
   //初始化
   for(int i = 1;i <= n;i++) cna[i] = 0,dis[i] = inf,cnt[i] = 0;
   dis[s] = 0;
   can[s] =1;
   p.push(s);
   while(!p.empty())
   {
     int now = p.front();
     p.pop();
     for(int i = point[now];i;i=nxt[i])
        if(dis[v[i]] > dis[u[i]] + w[i])
        {
           dis[v[i]] = dis[now] + w[i];
           cnt[v[i]] = cnt[u[i]] + 1;
           if(cnt[v[i]] >= n)
           { //负环
           }
           pre[v[i]]=now;
           if(!can[v[i]])
           {
            can[v[i]] = 1;
            p.push(i0;
           }
           
        }
       can[now]=0;
   }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值