zoukankan      html  css  js  c++  java
  • 最短路径--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

  • 相关阅读:
    《DSP using MATLAB》Problem 6.17
    一些老物件
    《DSP using MATLAB》Problem 6.16
    《DSP using MATLAB》Problem 6.15
    《DSP using MATLAB》Problem 6.14
    《DSP using MATLAB》Problem 6.13
    《DSP using MATLAB》Problem 6.12
    《DSP using MATLAB》Problem 6.11
    P1414 又是毕业季II
    Trie树
  • 原文地址:https://www.cnblogs.com/jkxsz2333/p/9520409.html
Copyright © 2011-2022 走看看