• 最短路径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

  • 相关阅读:
    Java的字符串及格式化输入输出
    Java的数据类型与类型转换
    java基本程序
    svn基础入门
    github基础入门笔记
    git基础入门笔记
    linux基础入门笔记
    二、FreeMarker 模版开发指南 第二章 数值和类型
    【CodeForces】[599B]Spongebob and Joke
    【CodeForces】[612B]HDD is Outdated Technology
  • 原文地址:https://www.cnblogs.com/syycjh/p/9520409.html
Copyright © 2020-2023  润新知