最短路径Floyd、Dijkstra、Bellman、SPFA算法

前言

  最短路径是数据结构-图中的一个经典问题,求解最短路径的问题,有四种算法,这四种算法各有各的不同,分别是:

  Floyd算法、Dijkstra算法、Bellman算法以及SPFA算法。

  最常用的是前两者,当然你也可以都掌握,毕竟各有各的好处。

1.Floyd-Warshall算法

  优点:实现代码极其简便,比较好理解。

  缺点:时间复杂度较高,为O(n3),适用于数据复杂度不高的题目

  此方法适用于解决多源图的最短路问题,用枚举的方式,遍历比较哪种路径最短。

  代码实现如下:

//floyd-warshall算法

for
(int k=1; k<=n; k++) { for(int i=1; i<=n; i++) { for(int j=1; j<=n; j++) { if(map[i][j]>map[i][k]+map[k][j]) { map[i][j]=map[i][k]+map[k][j]; } } } }

2.Dijkstra算法

  注意:这个算法只适用于无负权值的单元最短路问题,这种算法用到了贪心,和最小生成树的Prim算法有点类似

  思路:对每个找到的点进行标记,每标记一个点,就进行一次数据的更新,遍历完所有的点,确定最后的路径即为最短路径,就结束。

  时间复杂度:O(n2)  可进行优化,堆/优先队列优化后的时间复杂度:O(nlogn)

  代码实现如下:

//Dijkstra算法

#define inf 0x3f3f3f3f

for(i=1; i<n; i++)
{
    min=inf;
    for(j=1; j<=n; j++)   //求出当前dis数组中离第一个顶点最短的距离的顶点的下标
    {
        if(book[j]==0 && dis[j]<min)
        {
            min=dis[j];
            u=j;//记下这个点的下标
        }
    }
    book[u]=1;
    for(k=1; k<=n; k++)
    {
        if(a[u][k]<inf)
        {
            if(dis[k]>dis[u]+a[u][k])//若找到其他途径比从1号顶点直接到目的顶点的距离短,则替换掉
            {
                dis[k]=dis[u]+a[u][k];
            }
        }
    }
}

3.Bellman-Ford算法 O(NE)


  此方法适用于单源最短路径。

  优点:可以求出存在负边权情况下的最短路径。

  缺点:无法解决存在负权回路的情况。

  时间复杂度:O(NE),N是顶点数,E是边数。

  算法思想:很简单。一开始认为起点是“标记点”(dis[1] = 0),每一次都枚举所有的边,必然会有一些边,连接着“未标记的点”和“已标记的点”。

       因此每次都能用所有的“已标记的点”去修改所有的“未标记的点”,每次循环也必然会有至少一个“未标记的点”变为“已标记的点”。

  代码实现如下:


//Bellman-Ford算法
for(int i = 1; i <= n - 1; i++)
{
  for(int j = 1; j <= E; j++)  //注意要枚举所有边,不能枚举点
   {
     if(dis[u] + w[j] < dis[v])  //u, v分别是这条边连接的两个点
      {
        dis[v] = dis[u] + w[j]
        pre[v] = u;
      }
    }
}

4.SPFA算法 O(KE)

  适用于:稀疏图(侧重于对边的处理)。

  时间复杂度:O(KE),K是常数,平均值为二,E是边数。

  背景:SPFA是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。 

     这个算法简单地说就是队列优化的Bellman-Ford,利用了每个点不会更新次数太多的特点发明的此算法。 

     SPFA在形式上和广度优先搜索非常类似,不同的是广度优先搜索中的一个点出了队列就不可能重新进入队列,

     但是SPFA中的一个点可能在出队列之后再次被放入队列,也就是说一个点修改过其他的点之后,过了一段时间可能会获得更短的路径,

     于是再次用来修改其他的点,这样反复进行下去。

  优化方法:
    1.循环队列(可以降低队列大小)
    2.SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j) < dist(i),则将j插入队首,否则插入队尾。

//SLF优化
if(!vis[temp])
{
    if(dis[q[head + 1]] < dis[temp])  //注意小于号不要写反,否则时间会爆
      {
        tail = (++tail - 1) % qxun + 1;
        q[tail] = temp;
      }
    else
      {
        q[head] = temp;
        if(--head == 0) head = qxun;
      }
    vis[temp] = 1;
}

    3.LLL:Large Label Last 策略,设队首元素为i,每次弹出时进行判断,队列中所有dist值的平均值为x,

       若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。

  代码实现如下:

//SPFA算法
#define inf 0x3f3f3f3f
int spfa(int s,int n) { queue<int>q; memset(dis,inf,sizeof(dis)); dis[s]=0; memset(vis,0,sizeof(vis)); memset(c,0,sizeof(c)); q.push(s); vis[s]=1; flag=0; while(!q.empty()) { int x; x=q.front(); q.pop(); vis[x]=0; //队头元素出队,并且消除标记 for(int k=first[x]; k!=0; k=next[k]) //遍历顶点x的邻接表 { int y=v[k]; if(dis[x]+w[k]<dis[y]) { dis[y]=dis[x]+w[k]; //松弛 if(!vis[y]) //顶点y不在队内 { vis[y]=1; //标记 c[y]++; //统计次数 q.push(y); //入队 if(c[y]>n) //超过入队次数上限,说明有负环 return flag=0; } } } } }

参考资料:

   1.https://blog.csdn.net/zezzezzez/article/details/70245548

   2.https://blog.csdn.net/mashiro_ylb/article/details/78289790

   3.https://blog.csdn.net/tianhaobing/article/details/65443049

原文地址:https://www.cnblogs.com/syycjh/p/9520409.html