• 最短路


    写在前面的话:写写复习下它,太久没怎么写这类题了

    文章部分内容出自《算法竞赛进阶指南》

    单源最短路径

    这种问题就是说给一张有向图,以某一个节点(一般为1号节点),记录下其他每一个点

    到达这个1号节点的最短路径的长度。

    常用算法:Dijkstra,Bellman-Ford,SPFA(本质上是Bellman-Ford)

    Dijkstra算法

    基本思路:

    它是基于贪心算法的一种方案,只能用在所有边长度非负的图。因为若z为负,全局最小值不可能被其他点

    更新了。其中,第一步选出来的x已经满足--dist[x]是起点到x点的最短路径。本质上,我们在不断寻找全局

    最小值在进行标记和拓展,最后就得到起点1到每个节点的最短路。

    过程:

    1.初始化dist[i]数组,即原点到i点的距离,dist[1]=0,其他点初始化到原点距离为+∞;

    2.找出一个未被标记的且dist[x]最小的点x,再标记节点x;

    3.扫描x的所有出边,若dist[y]>dist[x]+z,就更新dist[y] = dist[x]+z;

    4.重复2--3,直到所有点被标记为止

    当然我们可以发现,在找全局最小值的时候,代码可以继续优化,即找这个最小值可以用二叉堆去找,

    复杂度从O($n^{2}$) 进化成 O($m log n$)。

    #include<iostream>
    #include<queue> 
    #include<cstring>
    #include<cstdio>
    using namespace std;
    const long MAXN=1000010;
    long n,m;
    long head[MAXN],Next[MAXN],ver[MAXN],edge[MAXN],tot=0;
    priority_queue< pair<long,long> > q;
    inline void add(long x,long y,long z){
        ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
    }
    bool v[MAXN];
    long dist[MAXN];
    inline void dijkstra(){
        for(long i=1;i<=n;i++) dist[i]=999999;
        memset(v,0,sizeof v);
        dist[1]=0;
        q.push(make_pair(0,1));
        while(q.size()){
            long x=q.top().second;
            q.pop();
            if(v[x]) continue;
            v[x]=1;
            for(long i=head[x];i;i=Next[i])
            {
                long y=ver[i],z=edge[i];
                if(dist[y]>dist[x]+z){
                dist[y]=dist[x]+z;
                q.push(make_pair(-dist[y],y));
                }
            }
        }
        
    }
    int main(){
        scanf("%ld%ld",&n,&m);
        long x,y,z;
        for(long i=1;i<=m;i++){
        scanf("%ld%ld%ld",&x,&y,&z);
        add(x,y,z);
        }
        dijkstra();
        for(long i=1;i<=n;i++)
        printf("%ld
    ",dist[i]); 
    }

    Bellman-Ford算法

    基本思路

    它是基于迭代的思想去考虑,即使所有边(x,y,z)满足三角形不等式:d[y]<=d[x]+z

    如下图:

    过程:

    1.扫描所有边,若dist[y]>dist[x]+z,就使dist[y]=dist[x]+z;

    2.重复上述步骤直到没有更新发生为止。

    复杂度高达O(nm),但它优于dijkstra的一点,是它的边权可以为负值。

    SPFA算法

    基本思路:

    spfa在国际上通称”队列优化的Bellman-Ford算法“,它用队列保存了待扩展的节点,每一次的入队

    相当于完成一次更新,使得节点慢慢收敛,即松弛,稀疏图效率高。

    因为对于一个从x到y的边,节点x还没有松弛过,那y就没必要松弛,所以我们用队列记录松弛过的点,

    避免了bellman-ford中的冗余松弛操作。

    过程:

    1.建立一个队列,初始入队节点1;

    2.取出队头,并扫描所有出边,若dist[y]>dist[x]+z,则更新dist[y]=dist[x]+z,并判断y是否在队列中,若

    不在,则将y入队;

    3.重复上述步骤直至队列为空。

    用简略语言概述就是,我们有一个松弛点x,尝试x能到达的点y,若y可以被松弛,就更新dist[y],在

    这个条件下,若y还没入队,那就把这个松驰过的点入队,直到队列为空。

    #include<iostream>
    #include<queue> 
    #include<cstring>
    #include<cstdio>
    using namespace std;
    const long MAXN=1000010;
    long n,m;
    long head[MAXN],Next[MAXN],ver[MAXN],edge[MAXN],tot=0;
    queue <long> q;
    inline void add(long x,long y,long z){
        ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
    }
    bool v[MAXN];
    long dist[MAXN];
    inline void spfa(){
        for(long i=1;i<=n;i++) dist[i]=999999;
        memset(v,0,sizeof v);
        dist[1]=0;
        q.push(1);
        v[1]=1;
        while(q.size()){
            long x=q.front();
            q.pop();
            v[x]=0;
            for(long i=head[x];i;i=Next[i])
            {
                long y=ver[i],z=edge[i];
                if(dist[y]>dist[x]+z){
                dist[y]=dist[x]+z;
                if(v[y]==0) q.push(y),v[y]=1;
                }
            }
        }
        
    }
    int main(){
        scanf("%ld%ld",&n,&m);
        long x,y,z;
        for(long i=1;i<=m;i++){
        scanf("%ld%ld%ld",&x,&y,&z);
        add(x,y,z);
        }
        spfa();
        for(long i=1;i<=n;i++)
        printf("%ld
    ",dist[i]); 
    }

    特别地,当我们要去判定负环的时候,我们可以用一个数组c[i]表示1到i的最短路径上点的个数,每一次松弛x-y时,

    我们就用c[y]=c[x]+1,当c[y]>n时,即存在负环。

    注:

    其实对比dijkstra和spfa,我们可以发现,dijkstra针对每一个最小距离点进行扩展,

    spfa则是通过迭代思想将所有边进行扩展,自然spfa的复杂度会高于dijkstra,但是spfa同时也具有

    可以更加容易加入各种其他算法和对负权的边,负环的处理。

    温馨提示,面对一个不存在负权边的图最好用dijkstra,因为spfa易被恶意卡掉。

    任意两点间的最短路径

    Floyd算法

    基本思路:

    它是一种基于动态规划的算法,我们可以用D[k,i,j]来表示i经过编号不超过k的节点后到达j的最短路,所以

    我们可以将其划分为两个子问题,一是congi经过编号不超过k-1的节点到j,或者从i经过k节点到达j。

    则 D[k,i,j]=min(D[k-1,i,j],D[k-1,i,k]+D[k-1,k,j])

    所以k是阶段,应该放在最外层

    我们也可以用D保存邻接矩阵,省略k这一维,即:

    D[i,j]=min(D[i,j],D[i,k]+D[k,j])

    也可以理解为从i到j,看看目前的情况好,还是再经过一个中间点k更好。

    过程:

    这就不再赘述了,太过简单......

    #include<iostream>
    #include<cmath>
    using namespace std;
    const long MAXN=10010;
    long dist[MAXN][MAXN];
    long n,m;
    int main(){
        cin>>n>>m;
        for(long i=1;i<=n;i++)
        for(long j=1;j<=n;j++) 
        dist[i][j]=999999;
        for(long i=1;i<=n;i++) 
        d[i][i]=0;
        long x,y,z;
        for(long i=1;i<=m;i++){
            cin>>x>>y>>z;
            if(dist[x][y]>z) dist[x][y]=z;
        }
        for(long k=1;k<=n;k++)
        for(long i=1;i<=n;i++)
        for(long j=1;j<=n;j++)
        dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
        for(long i=1;i<=n;i++){
        for(long j=1;j<=n;j++)
        cout<<dist[i][j]<<" ";
        cout<<endl;
        }
    }

    范围,数值最大值可以自行更改

    传递闭包

    在交际网络中存在若干元素和若干对二元关系,关系具有传递性,请推导出尽量多的元素间的关系----传递闭包

    易得,我们可以用floyd解决此类问题

    #include<iostream>
    #include<cmath>
    using namespace std;
    const long MAXN=10010;
    bool dist[MAXN][MAXN];
    long n,m;
    int main(){
        cin>>n>>m;
        for(long i=1;i<=n;i++) 
        dist[i][i]=1;
        long x,y,z;
        for(long i=1;i<=m;i++){
            cin>>x>>y;
            dist[x][y]=dist[y][x]=1;
        }
        for(long k=1;k<=n;k++)
        for(long i=1;i<=n;i++)
        for(long j=1;j<=n;j++)
        dist[i][j]|=dist[i][k]&dist[k][j];
        
    }
  • 相关阅读:
    c# 时间戳转换为Datetime类型的时间
    WCF小白初试 错误之一:“有零个应用程序终结点”的解决办法
    对html制作新手的一些建议,大牛可以忽略
    Aspose.Words导出dt到word的问题
    获取checkboxlist选中的值以及绑定来自之前选中的来自数据库的值
    excel导入mssql数据库,支持excel2003--2010文件格式
    vscode调试angular
    asp.net web api 跨域问题
    WPF 完美截图 <二>
    EF错误
  • 原文地址:https://www.cnblogs.com/yuzhe123/p/13363222.html
Copyright © 2020-2023  润新知