【Floyed +Dijkstra + Bellman-Ford + SPFA】四种最短路算法

一个图中,求点u到达点v的最短路径长度,常用的有四种算法:

一、 Floyed算法

可以求出多源最短路可以处理负权边的情况,但是不能出现负环

Floyed算法使用的是动态规划的方法。

设d[i][j][k]表示i到j只经过1,2…k这些结点时,i到j的最短路距离。会出现以下两种情况:

(1) 经过k点:

   d[i][j][k]=d[i][k][k-1]+d[k][j][k-1]。

(2) 不经过k点:

d[i][j][k]=d[i][j][k-1]。

       所以状态转移方程为:

              d[i][j][k]=min{d[i][k][k-1]+d[k][j][k-1],d[i][j][k-1]}

       边界条件:

              d[i][j][0]=w[i][j](连接i到j的边的权值),不存在的边权设为+∞。

       因为k是递增的,d[i][j]保存的状态是d[i][j][k-1],所以可以减少一维,使用二维数组。

       状态转移方程:    

              d[i][j]=min{d[i][k]+d[k][j],d[i][j]}。

       边界条件:

              d[i][j]=w[i][j]。

       枚举k,使用中间点k来更新i到j的最短路距离。

       【代码实现】

for(k=1;k<=n;k++) //枚举中间
    for(i=1;i<=n;i++) //枚举起点        
        for(j=1;j<=n;j++)          //枚举终点
            d[i][j]=min(d[i][k]+d[k][j],d[i][j]);

    时间复杂度:O(n3)

 

二、Dijkstra算法

求单源最短路径,不能处理负权。

Dijkstra算法使用的是贪心方法,d[i]表示起点s0到i的最短距离。

从起点s0开始,选择未访问过的离s0最近的一个点i,也就是最小的d[i],因为所以边权为正,不会存在更短的路径到达i,保证了贪心的正确性。然后将i作为中间点,更新经过i可到达的点的最短路距离,继续贪心寻找未访问过的最近的一个点,经过n次贪心,算法结束。

【算法步骤】

(1) 初始化d[s0]=0,其他d[i]=+∞。

(2) 经过n次贪心,找到起点s0到其他点的最短路距离

a)  找出一个未访问过的最小的d[k]

b)  标记k被访问过

c)   将k作为中间点,更新起点s0到经过k到达其他点v的d[v]

【图解算法】

《【Floyed +Dijkstra + Bellman-Ford + SPFA】四种最短路算法》

《【Floyed +Dijkstra + Bellman-Ford + SPFA】四种最短路算法》

【代码实现】

 for(i=1;k<=n;k++)
{
    maxn=0x7fffffff;
    for(j=1;j<=n;j++)                 //找出未访问最小的d[j]
    {
        if( !vis[j] && d[j]<maxn)
       {
             maxn=d[j];k=i;
        }

    }
    vis[k]=1;
    for(j=1;j<=n;j++)          //k作为中间点,更新起点经过k到达其他点的d[j]
        if(w[k][j])      d[j]=min{d[k]+w[k][j],d[j]};
}

 时间复杂度:O(n2)

【Dijkstra算法的优化】

由于每次要寻找未访问过的最小的d[i],都要花费O(n)的时间,可以使用堆进行优化。

 

三、 Bellman-Ford算法

可以求单源最短路径,可以处理负权边的情况,还可以判断是否出现负权回路。

d[i]表示起点s0到i的最短距离。

图的任意一条最短路既不会包含负权回路,也不会包含正权回路,最多包含n-1条边。那么,从源点s可以到达的结点如果存在最短路,则最短路构成了一棵以s为根的最短路树。因此,可以按照距离根s的层次,逐层生成到达每个点的最短路,这就是Bellman-Ford算法的迭代松弛操作。

对每条边进行第一遍松弛操作的时候,生成了从s出发,层数为1的最短路。也就是找到了从s出发,经过1条边到达其他点的最短路(注意此时的最短路是当前层数的最短路,不一定是最终的答案,是一个估计值,可能从第2层某点到达第1层更短,如下图得s-a-b)。对每条边进行第二次松弛操作时,生成了从s出发,层数为2的的最短路,也就是从s出发,经过2条边到达其他点的最短路,以此类推。因为最短路最多含有n-1条边,所以最多进行n-1次松弛操作。

如果经过n-1次松弛后,d[i]还是+∞,说明s不可到达i。

如果经过n-1次松弛后,还能继续松弛,说明有负权回路存在,如果n-1次松弛后,如果有边(u,v)满足d[v]>d[u]+w[u][v],说明还能继续松弛,存在负权回路。

【算法步骤】

(1) 初始化:d[s]=0,d[i]= +∞。

(2) 进行n-1次松弛操作

【图解算法】

《【Floyed +Dijkstra + Bellman-Ford + SPFA】四种最短路算法》

【代码实现】

for(i=1;i<=n-1;i++)              //n-1次松弛操作
      for(j=1;j<=m;j++)         //m条边
             if( d[edge[j].u]+edge[j].w<d[edge[j].v])
                        d[edge[j].v]= d[edge[j].u]+edge[j].w;
      for(j=1;j<=m;j++)
            if(d[edge[j].u]+edge[j].w<d[edge[j].v])  flag=1; //存在负权回路

实际复杂度:O(n*m)

 

四、SPFA算法

Bellman-Ford算法中,每次的松弛操作并不需要对所以边进行松弛操作,只需要与当前找到的最短路的点相连的边进行松弛,例如图中,第一次松弛时,只需要松弛(s,a),(s,b),其他边不需要松弛,实际上也无法松弛。因此可以对Bellman-Ford算法进行优化,每次对可以松弛的边进行松弛,不能进行松弛的边不作处理。而SPFA算法就是对Bellman-Ford算法使用队列优化。

设立一个先进先出的优先队列,优先取出队首的点u,用u当前计算出的最短路估计值对边(u,v)进行松弛操作,如果v的最短路估计值有所减小,且v不在队列中,就将队列v入队,放入队尾。这样不断取出结点来进行松弛操作,直到队列为空。

SPFA仍然可以判断是否存在负权回路,如果一个结点弹出/进入队列次数超过了n-1次,说明存在负权回路,可以添加一个数组用来记录每个点的入队次数。

【算法步骤】

(1) 初始化:dis[s]=0,dis[i]= +∞,新建一个队列,将源点s入队,标记s已经入队。

(2) 从队首取出一个点u,标记u已经出队,将与u有边相连的点v进行松弛,,如果松弛成功,判断v是否在队列中,如果没有入队,标记v入队。继续此步骤,直到队列为空。

【代码实现】

       q.push(s);
       vis[s]=1;  //源点s入队,标记入队
       while(q.size())
       {
              u=q.front();q.pop();vis[u]=0;        //取出队头,标记未入队
              for(i=head[u];i;i=next[i])
              {
                     v=ver[i];
                     w=edge[i];
                     if(dis[u]+w<dis[v])
                     {
                           dis[v]=dis[u]+w;
                           if(!vis[v])   {q.push(v);vis[v]=1;}    //如果没有在队列,入队,标记已入队
                     }    
              }
       }

时间复杂度:对于稀疏图,为O(km),k为较小的常数,而对于稠密图或者构造的网格图,会提高到O(n*m)。

    原文作者:Dijkstra算法
    原文地址: https://blog.csdn.net/strve/article/details/80957491
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞