zoukankan      html  css  js  c++  java
  • [OI学习笔记]DAG最短路的四种算法整理-floyd,Dijkstra,Bellman-Ford,SPFA

    背景

      开学了,好开心啊!

        周末好不容易写篇博客,搞长一点把。。。

    最短路概念

        这周花了点时间研究最短路问题,那么什么是最短路呢?

        摘自百度百科:

        最短路问题(short-path problem)是网络理论解决的典型问题之一,可用来解决管路铺设、线路安装、厂区布局和设备更新等实际问题。基本内容是:若网络中的每条边都有一个数值(长度、成本、时间等),则找出两节点(通常是源节点和阱节点)之间总权和最小的路径就是最短路问题。 [1] 

        我了解的最短路算法有四种——floyd,Dijkstra,Bellman-Ford,SPFA,四种算法各有各的特点,适用的图也有不同。

    松弛操作

        在学习最短路算法前,首先要了解松弛操作,这是所有最短路算法的核心,即是对于一条边(u,v,w),比较以中间点k或不以k当中间点时的最短路那个最小,选择小的那条来更新最短路:

    if(dist[u]+w<dist[v])dist[v]=dist[u]+w;

    (A)Floyd算法  时间复杂度O(n3)

            1)基于动态规划,本质上是一个三维的DP

            2)与其他算法不同,floyd是一个多源最短路径算法,即经过一次floyd后能求出任意两点间的最短路

            3)基本思想:dist[i][j][k]是i到j的只以1-k为中间路点的最短路,则dist[i][j][k]=min(dist[i][j][k-1],dist[i][k][k-1]+dist[k][j][k-1]);即选择不以k或以k当中间点时的最小的dist为dist[i][j][k]

            4)具体实现:

                ▶初始化:dist[s][v]=l(s,v)【如果存在边(s,v)】

                ▶进行松弛操作

            3)核心代码

    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(dist[i][k]+dist[k][j]<dist[i][j])
                    dist[i][j]=dist[i][k]+dist[k][j];

    (B)Dijkstra算法  时间复杂度:堆优化:O((n+m)*log m)  无优化:O(nm)

        1)只适用于无负边权的图(有负边要么TLE要么WA)

        2)基本思想:不断用已更新的边去更新他所连接的边。

        3)具体实现:

            ▶初始化:dist[s]=0;其余dist[i]=INF;然后把s点加入待处理队列

            ▶更新:每次以队首节点为基础更新

        4)

           Q :如果要求具体的最短路径怎么办?

          A :在更新时如果可以更新就用一个path数组存储这个点是从那个点更新来的,输出时带着输出就可以了

        5)

            Q:怎么写堆优化?

           A:用一个优先队列(原理是小根堆)存储每次更新了的点,依次用这些点去更新这个点所连的边,更新完后就让他出队

        6)代码:

    #include <bits/stdc++.h>
    #define re register
    using namespace std;
    
    inline int read() {
        int X=0,w=1; char c=getchar();
        while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
        while (c>='0'&&c<='9') X=(X<<3)+(X<<1)+c-'0',c=getchar();
        return X*w;
    }
    
    struct Edge { int v,w,nxt; };
    Edge e[500010];
    int head[100010],cnt=0;
    
    inline void addEdge(int u,int v,int w) {
        e[++cnt].v=v;
        e[cnt].w=w;
        e[cnt].nxt=head[u];
        head[u]=cnt;
    }
    
    int n,m,s;
    int dis[100010];
    
    struct node { //堆节点
        int u,d;//存储每次更新到的点和它的dist
        //重载运算
        bool operator <(const node& rhs) const {
            return d>rhs.d;
        }
    };
    
    inline void Dijkstra() {
        for (re int i=1;i<=n;i++) dis[i]=2147483647;
        dis[s]=0;
        priority_queue<node> Q; //
        Q.push((node){s,0});
        while (!Q.empty()) {
            node fr=Q.top(); Q.pop();
            int u=fr.u,d=fr.d;
            if (d!=dis[u]) continue;
            for (re int i=head[u];i;i=e[i].nxt) {
                int v=e[i].v,w=e[i].w;
                if (dis[u]+w<dis[v]) {
                    dis[v]=dis[u]+w;
                    Q.push((node){v,dis[v]});
                }
            }
        }
    }
    
    int main() {
        n=read(),m=read(),s=read();
        for (re int i=1;i<=m;i++) {
            int X=read(),Y=read(),Z=read();
            addEdge(X,Y,Z);
        }
        Dijkstra();
        for (re int i=1;i<=n;i++) printf("%d ",dis[i]);
        return 0;
    }

    (C)Bellman-ford算法  时间复杂度:O(mn)

        1)基于动态规划

        2)可以用来判负环

        3)如果没有负环,至多更新n-1轮或者某轮没有更新即可出解

        4)判负环:如果更新第n-1环之后还有边能更新,就有负环

        5)具体实现:

            1)dist[s]=0;其余=INF

            2)更新n-1轮,其中每轮:

                1)对于每一条边,如果if(dist[u]+w<dist[v])dist[v]=dist[u]+w;并且把updated标记改为有更新

                2)更完每条边后,如果updateed标记是没更新,就break,因为这时已经更新结束,并且这样肯定没负环

                3)最后把updated改为0,进行下一轮

            4)再单独更新一轮,如果这单独一轮中有更新,就判为有负环

        6)生动形象的演示:

        

    #include<cstdio>
    #define INF 2147483647
    #define MAX 10010
    struct Edge{
        int u,v,w;
    }edge[MAX];
    int n,m,s,t,dist[MAX];
    void BellmanFord(){
        int updated=0;
        for(int j=1;j<=n-1;j++){
            for(int i=1;i<=m;i++)
                if(dist[edge[i].u]+edge[i].w<dist[edge[i].v]){
                    dist[edge[i].v]=dist[edge[i].u]+edge[i].w;
                    updated=1;
                }
            if(!updated)break;
            updated=0;
        }
        for(int i=1;i<=m;i++)
            if(dist[edge[i].u]+edge[i].w<dist[edge[i].v]){
                printf("Orz");
                return;
            }
        printf("%d",dist[t]);    
    }
    int main(){
        scanf("%d%d%d%d",&n,&m,&s,&t);
        int X,Y,Z;
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&X,&Y,&Z);
            edge[i].u=X;
            edge[i].v=Y;
            edge[i].w=Z;
        }
        BellmanFord();
        return 0;
    }

    (D)SPFA 常被卡 时间复杂度:最坏:O(nm)  一般:不知道

        1)关于SPFA,他死了

        2)原因是在NOI中Dijkstra和SPFA这对难兄难弟经常其中一个被卡数据导致不能通过,而SPFA最经常

        3)和Dijkstra有点像,我有时候都分不清

        3)SPFA也是用一个队列(不是优先队列),第一步从s点开始,s入队,对于队中的每一个元素,广度遍历其所有出边(u,v,w),并对其进行松弛,如果松弛可以进行,就让v入队,搜算完成后,让u出队,进行下一轮搜索,直至队列空

        4)是bellmanford的优化

        5)具体实现:

            1)初始化:dist[s]=0;其余=INF;

            2)从s开始搜索,进行松弛,直至队列空

        6)代码:

    #include <bits/stdc++.h>
    #define ll long long
    #define MAXM 500005
    #define MAXN 10005
    using namespace std;
        int n,m,s,sz,st,en;
        int Q[4000005],f[MAXN],to[MAXM<<1],nex[MAXM<<1],v[MAXM<<1],las[MAXN];
        bool inq[MAXN];
    int inline read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    inline void ins(int x,int y,int z)
    {
        sz++;to[sz]=y;v[sz]=z;nex[sz]=las[x];las[x]=sz;
    }
    void spfa()
    {
        memset(f,127/2,sizeof(f));
        f[s]=0;Q[st=en=1]=s;
        while (st<=en)
        {
            int x=Q[st++];
            inq[x]=false;
            for (int i=las[x];i;i=nex[i])
            if (f[x]+v[i]<f[to[i]])
            {
                f[to[i]]=f[x]+v[i];
                if (!inq[to[i]])
                {
                    inq[to[i]]=true;
                    Q[++en]=to[i];
                }
            }
        }
    }
    int main()
    {
        n=read(),m=read(),s=read();
        for (int i=1;i<=m;i++)
        {
            int x=read(),y=read(),z=read();
            ins(x,y,z);
        }
        spfa(); 
        for (int i=1;i<=n;i++)
            printf("%d%c",f[i]>1e9?2147483647:f[i],i==n?'
    ':' ');
        return 0;
    }

        以上就是关于DAG最短路的算法,各种算法各有各的优缺点,主要体现在时空复杂度上,要谨慎使用

        一道模板题(普及-难度)

        关于图论的内容的话再写一个拓补排序就不写了(表示图论从暑假看到现在已经看吐了)

        求推荐.

    本篇文章为SHINE_GEEK原创,转载请注明来源!
    written_by:SHINE_GEEK

    -------------------------------------
    签名:自己选的路,跪着也要走完;理想的实现,需要不懈奋斗!
    -------------------------------------
  • 相关阅读:
    Java复制数组
    关于js正则表达式的理解
    js声明const, var, let的区别
    原生js删除多个相同类名的子元素
    python -反射hasattr、setattr、delattr
    Python-反射getattr的应用
    Python-库安装
    python -函数
    Appium -作业5(2)
    Appium appium1.6.5 使用 set_value () 输入中文,真机上无显示
  • 原文地址:https://www.cnblogs.com/sjrb/p/9607238.html
Copyright © 2011-2022 走看看