zoukankan      html  css  js  c++  java
  • 最短路

    在这里插入图片描述n表示点的数量 m表示边的数量
    朴素版迪杰斯特拉 O(n^2)适合稠密图(n ^2约等于m)
    题目链接:https://www.acwing.com/problem/content/851/

    共有n个点 起点是1 剩余n-1个点的距离未知 所以只需循环n-1次就可以确定所有点到1的最短距离 n点的最短距离距一定出来了。每次循环从没有确定最短路的点集(标记为0)中找到最近的那个点 再用这个点去更新其他与他相连的点(自环应该是没有影响的,走过自环到这一点一定不是最短路 因为边权大于0)

    #include<iostream>
    #include<cstring>
    using namespace std;
    
    int n,m,g[510][510];//一定要开大一点!!!
    int dist[510];
    bool st[510];//已经确定最短路了就标记为1
    void dij()
    {
        memset(dist,0x3f,sizeof dist);
        dist[1]=0;//这里一定不能标记点1,还要用一点去更新其他点
        for(int i=1;i<n;i++)
        {
            int t=-1;
            for(int j=1;j<=n;j++)//寻找没有确定最短路点中的最近的点
                if(!st[j]&&(t==-1||dist[t]>dist[j]))
                    t=j;
            for(int j=1;j<=n;j++)//更新其他点
                dist[j]=min(dist[t]+g[t][j],dist[j]);
            st[t]=1;
        }
        if(dist[n]==0x3f3f3f3f) cout<<-1<<endl;
        else cout<<dist[n]<<endl;
        return ;
    }
    int main()
    {
        memset(g,0x3f,sizeof g);//因为有重边 初始化最大 取最小
        cin>>n>>m;
        while(m--)
        {
            int a,b,c;
            cin>>a>>b>>c;
            g[a][b]=min(g[a][b],c);//稠密图 用矩阵比较好写
        }
        dij();
        return 0;
    }
    
    

    堆优化迪杰斯特拉 O(mlogn)适合稀疏图
    优化思路 是在朴素版的基础上 通过优先队列(又叫堆)只用O(1)来寻找最小值
    其他地方与朴素版是一样的 就是每次从队列弹出的点有可能已经求得最短路了 continue 就行了
    链接:https://www.acwing.com/problem/content/852/

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=100010;
    typedef pair<int,int> PII;
    
    int n,m,h[maxn],ne[maxn],e[maxn],w[maxn],dist[maxn],idx;
    void add(int a,int b,int c)
    {
        e[idx]=b; w[idx]=c;
        ne[idx]=h[a];
        h[a]=idx++;
    }
    bool st[maxn];
    int dij()
    {
        dist[1]=0;
        priority_queue<PII,vector<PII>,greater<PII> >heap;//PII类型的优先队列,优先排序first 
        heap.push({0,1});
        while(!heap.empty())
        {
            auto x=heap.top();
            heap.pop();
            int u=x.second;
            if(st[u]) continue;
            for(int i=h[u];i!=-1;i=ne[i])//更新距离
            {
                int v=e[i];
                if(dist[v]>dist[u]+w[i])
                {
                    dist[v]=dist[u]+w[i];
                    heap.push({dist[v],v});
                }
            }
            st[u]=1;//别忘了标记
        }
        if(dist[n]==0x3f3f3f3f) return -1;
        else return dist[n];
    }
    
    int main()
    {
        memset(h,-1,sizeof h);//初始化不能忘
        memset(dist,0x3f,sizeof dist);
        cin>>n>>m;
        while(m--)
        {
            int a,b,c;
            cin>>a>>b>>c;
            add(a,b,c);
        }
        cout<<dij();
    
        return 0;
    }
    

    Bellman-Ford算法 O(nm) 可以做有边数限制的负边权最短路
    链接:https://www.acwing.com/problem/content/submission/855/
    如果有边数限制k次 外层循环看k次,如果没有的话就循环n次;内层循环一直都是循环m次 一直去更新最小值 就可以求得最短路了(虽然不知道为什么,但就是这么神奇)还有一点是每次循环有可能发生串联 外层循环就是表示经过的边数
    在这里插入图片描述
    假如有这种情况在第1次循环是表示经过一条边到达点的最小距离 点1到点3应该是等于4 但是如果点2更新过了再去更新点3 就会更新为 3 就相当于串联一样没有边数限制的话结果不会有影响 有的话就有可能了

    #include<iostream>
    #include<cstring>
    using namespace std;
    const int maxn=100010;
    int idx,dist[maxn];
    struct node
    {
        int u,v,w;
    }ed[maxn];
    
    int main()
    {
        memset(dist,0x3f,sizeof dist);//初始化
        int n,k,m;
        cin>>n>>m>>k;
        for(int i=0;i<m;i++)
        {
            int a,b,c;
            cin>>a>>b>>c;
            ed[idx++]={a,b,c};
        }
        dist[1]=0;
        for(int i=0;i<k;i++)
        {
            int d[maxn];
            for(int i=1;i<=n;i++)
                d[i]=dist[i];
            for(int j=0;j<m;j++)
            {
                int u=ed[j].u,v=ed[j].v,w=ed[j].w;
                if(dist[v]>d[u]+w)
                        dist[v]=d[u]+w;
            }
        }
        if(dist[n]>0x3f3f3f3f/2) cout<<"impossible"<<endl;//如果点n到不了但是有到点n的负权边 dist[n]就不会是inf了但是它再怎么减也不会小于inf/2
        else cout<<dist[n]<<endl;
        return 0;
    }
    

    spfa (bellman算法的优化版) 一般是O(m)最坏情况下可以卡成O(nm)
    可以解决负边权问题但是没有边数限制 一般要比bellman算法要快

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=100010;
    int h[maxn],e[maxn],ne[maxn],w[maxn],idx,dist[maxn];
    bool st[maxn];//标记是否在队列里 切记!!!
    int n,m;
    void add(int a,int b,int c)
    {
        e[idx]=b;
        w[idx]=c;
        ne[idx]=h[a];
        h[a]=idx++;
    }
    void spfa()
    {
        dist[1]=0;
        queue<int> q;
        q.push(1);
        st[1]=1;
        while(q.size())
        {
            int u=q.front();
            q.pop();
            st[u]=0;//弹出后要抹去标记
            for(int i=h[u];i!=-1;i=ne[i])
            {
                int v=e[i];
                if(dist[v]>dist[u]+w[i])
                {
                    dist[v]=dist[u]+w[i];
                    if(!st[v])//不在队列 入队并标记
                    {
                        q.push(v);
                        st[v]=1;
                    }
                }
            }
        }
        if(dist[n]>=0x3f3f3f3f/2) cout<<"impossible";
        else cout<<dist[n];
    }
    
    int main()
    {
        memset(dist,0x3f,sizeof dist);
        memset(h,-1,sizeof h);
        cin>>n>>m;
        while(m--)
        {
            int a,b,c;
            cin>>a>>b>>c;
            //if(b==n) cout<<a<<endl;
            add(a,b,c);
        }
        spfa();
        return 0;
    }
    

    spfa可以来判断是否有负环 开一个数组来表示到某一点的最短路经过的点数如果点数大于n 有抽屉原理可知路径上一定有重复点 就一定有负环了

    弗洛伊德算法 多源最短路唯一知道的也是最简单的算法 O(n^3)基于动态规划
    k是状态 所以最外层枚举 (不是很理解)
    链接:https://www.acwing.com/problem/content/856/

    #include<iostream>
    #include<cstring>
    using namespace std;
    const int maxn=250,inf=0x3f3f3f3f;
    
    int g[maxn][maxn];
    int n,m,k;
    
    int main()
    {
        cin>>n>>m>>k;
        for(int i=1;i<=n;i++)//初始化
            for(int j=1;j<=n;j++)
                if(i!=j)g[i][j]=inf;
                else g[i][j]=0;
        while(m--)
        {
            int a,b,c;
            cin>>a>>b>>c;
            g[a][b]=min(g[a][b],c);
        }
        for(int k=1;k<=n;k++)
            for(int i=1;i<=n;i++)
                for(int j=1;j<=n;j++)
                        g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
        while(k--)//k次询问
        {
            int a,b;
            cin>>a>>b;
                if(g[a][b]<0x3f3f3f3f/2)//负边权的原因
                    cout<<g[a][b]<<endl;
                else puts("impossible");
        }
        
    }
    
  • 相关阅读:
    linux/windows nginx安装
    linux/windows vsftpd安装
    linux 操作命令
    linux/windows java tomcat安装
    常见的Activity Action Intent常量
    Intent.ACTION_PICK
    Android实现抽奖转盘
    Android-短信验证
    Android-多平台分享(新浪微博)
    Android 手势滑动,多点触摸放大缩小图片
  • 原文地址:https://www.cnblogs.com/neflibata/p/12871805.html
Copyright © 2011-2022 走看看