zoukankan      html  css  js  c++  java
  • 第三关——图论:最短路

    14:52:09 今天天气好晴朗,处处好风光。

    阳光犹如希望,我即将要回去打游戏的希望,希望那么那么大,那么那么刺眼!!!

    好的,接下来为您播报考试后续情况:不算太差,还看得过去,至少,应该能活着把年过过去。接下来,就介绍一下在父母看到成绩后想要打你时如何通过最短路逃跑

    目录

    • Dijkstra算法

    • Bellman-Ford算法

    • SPFA算法

    • Floyd算法

    首先,要来简单论述一下“最短路”这个问题。要求最短路,无论什么方法,都要先存图。

    百“路”图为先

     那么,图分为两种,有向图与无向图。无论是有向图还是无向图,存图方式都是一样的。分为两种:

    • 邻接矩阵(空间复杂度:O(n2))

    可以说,矩阵存图是非常非常的简单易打了。话不多说,放代码

    int n,m;
        cin>>n>>m;
        memset(e,127,sizeof(e));//将矩阵中所有边初始化为无穷大
        for(int i=1; i<=n; i++) e[i][i]=0;
        for(int i=1; i<=m; i++) {
            int x,y,z;
            cin>>x>>y>>z;//x点与y点有一条边,边的权值为z
            e[x][y]=z;
            e[y][x]=z;
        }
    • 邻接表(空间复杂度:O(n+m))

    邻接表可以用数组模拟链表指针:

    void add(int x,int y,int z)//x点与y点有一条边,边的权值为z
    {
        ver[++tot]=y,edge[tot]=z; 
        next[tot]=head[x],head[x]=tot; 
    } 
    for(int i=head[x];i;i=next[i])
    {
        int y=ver[i],z=edge[i]; 
    }//于主函数中

    也可以用vector存储:

    vector<int>e[1000];//vector代替邻接表
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        scanf("%d%d",&x,&y);//x,y两点间有一条边 
        e[x].push_back(y);//加入x的vector
    }

    好的,接下来开始讲我们的单源最短路径的几种算法(单源最短路径:源点到每个点的距离)

    • Dijkstra算法(基于贪心思想,适用于没有负权的图)

    算法流程

    1. 初始化dis[1]=0,其余dis数值为正无穷大

    2. 找到未被标记的最小节点x,并标记

    3. 扫描x的所有出边,若dis[y]>dis[x]+z(边权),则用dis[x]+z更新dis[y]的值

    4. 直到节点被标记完为止

    5. dis[x]代表源点到x的最短路径。

    6. 它的时间复杂度为(O(logn))

    单源最短路问题

    题目描述

    输入一个无向网络,输出其中2个顶点之间的最短路径长度

    输入

    输入文件第一行为n和m,表示有n个顶点和m条带权边,其中顶点编号是从1到n,接下来有m行,每行三个整数,分别表示两个顶点编号和对应边的权值,再接下来有一行,两个整数表示要求的最短路径的两个顶点编号

    输出

    输出文件就一行,即两个顶点间的最短路径长度(权值和)

    输入样例

    4 5 1 2 2 1 3 1 2 3 2 2 4 1 3 4 6 1 4

    输出样例

    3

    这里需要用vis数组存储是否标记。

    #include<bits/stdc++.h>
    using namespace std;
    int e[1000][1000],dis[1000],vis[1000];
    int main() {
        int n,m;
        cin>>n>>m;
        memset(e,127,sizeof(e));
        for(int i=1; i<=n; i++) e[i][i]=0;
        for(int i=1; i<=m; i++) {
            int x,y,z;
            cin>>x>>y>>z;
            e[x][y]=z;
            e[y][x]=z;
        }
        int s,b;
        cin>>s>>b;
        for(int i=1; i<=n; i++) {
            dis[i]=e[s][i];
        }
        vis[s]=1;
        for(int i=1; i<=n; i++) {
            int mmin=99999,k;
            for(int j=1; j<=n; j++) {
                if(vis[j]!=1) {
                    if(dis[j]<mmin) {
                        mmin=dis[j];
                        k=j;
                    }
                }
            }
            vis[k]=1;
            for(int j=1; j<=n; j++) {
                dis[j]=min(dis[j],dis[k]+e[k][j]);
            }
        }
        cout<<dis[b];
        return 0;
    }
    • Dijkstra堆优化(时间复杂度(O(n2)))

    堆排序

    #include<iostream>
    #include<queue>
    using namespace std;
    priority_queue<int, vector<int>, greater<int> >q;
    int main(){
        int n,x;
        cin>>n;
        for(int i=1;i<=n;i++){
            cin>>x;
            q.push(x);
        }
        while(!q.empty()){
            cout<<q.top()<<" ";
            q.pop();
        }
        return 0;
    } 

    https://www.luogu.com.cn/problem/P4779

    所以就有了堆优化的Dijkstra

    堆中每个元素存两个值 结点编号xx和入堆是该点被更新成的距离dis

    dis为第一关键字(即按dis的小根堆) 主要思想不变 每次找距离最小点是直接取出堆顶并删除堆顶

    更新最短路的时候 将更新后的节点入堆

    注意一个点可能会被更新多次而入堆多次 但是只有最后一次入堆才是正确的dis 同时也一定是在所有入堆操作结束后才会出堆 所以直接开个数组判断有没有出堆过就好了 当然也可以根据堆中元素的距离大小和点的最短路大小直接判断

    #include<bits/stdc++.h>
    #define M(x,y) make_pair(x,y)
    using namespace std;
    int fr[100010],to[200010],nex[200010],v[200010],tl,d[100010];
    bool b[100010];
    void add(int x,int y,int w){
        to[++tl]=y;
        v[tl]=w;
        nex[tl]=fr[x];
        fr[x]=tl;
    }
    priority_queue< pair<int,int> > q;
    int main(){
        int n,m,x,y,z,s;
        scanf("%d%d%d",&n,&m,&s);
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
        }
        for(int i=1;i<=n;i++) d[i]=1e10;
        d[s]=0;
        q.push(M(0,s));
        while(!q.empty()){
            int x=q.top().second;
            q.pop(); 
            if(b[x]) continue;
            b[x]=1;
            for(int i=fr[x];i;i=nex[i]){
                int y=to[i],l=v[i];
                if(d[y]>d[x]+l){
                    d[y]=d[x]+l;
                    q.push(M(-d[y],y));
                }
            }
        }
        for(int i=1;i<=n;i++) printf("%d ",d[i]);
        return 0;
    }
    • Bellman-Ford算法和SPFA算法

    Bellman-Ford算法

    算法流程:

    1. 扫描所有边(x,y,z),若dis[y]>dis[x]+z(边权),则用dis[x]+z更新dis[y]的值

    2. 重复直到没有更新

    3. 时间复杂度(O(nm))

    SPFA算法

    算法流程:

    1. 建立一个队列,最初队列只含起点1

    2. 取队头结点x,扫描它的所有出边,若dis[y]>dis[x]+z(边权),则用dis[x]+z更新dis[y]的值

    3. 若y不在队列中的话,将y入队

    4. 重复直到队列为空

    5. 时间复杂度(O(km))

    6. 可解决负权,也可判断有无负环

    可惜的是,该算法已死。

    http://noi-test.zzstep.com/contest/0x60%E3%80%8C%E5%9B%BE%E8%AE%BA%E3%80%8D%E4%BE%8B%E9%A2%98/6101%20%E6%9C%80%E4%BC%98%E8%B4%B8%E6%98%93

    以1为起点,在原图上使用SPFA算法,用数组d[x]表示在原图中从节点1到节点x的所有路径中,能够经过的权值最小的节点的权值

    以n为起点,在反图上使用SPFA算法,用数组f[x]表示在原图中从节点x到节点n的所有路径中(反图中是n到x),能够经过的权值最大的节点的权值

    最后,枚举每个节点x,用f[x]-d[x]更新答案

    #include<bits/stdc++.h>
    using namespace std;
    const int N=100002;
    int a[N],b[N],c[N];
    vector<int> g[N];
    vector<int> s[N];
    queue<int> q;
    int main()
    {
        int n,m;
        cin>>n>>m;
        for(int i=1;i<=n;++i)
        cin>>a[i];
        for(int i=1;i<=m;i++)
        {
            int x,y,z;
            cin>>x>>y>>z;
            g[x].push_back(y);
            s[y].push_back(x);
            if(z==2)
            {
                g[y].push_back(x);
                s[x].push_back(y);
            }
        }
        q.push(1);
        memset(c,60,sizeof(c));
        c[1]=9999999;
        while(!q.empty())
        {
            int t=q.front();
            q.pop();
            c[t]=min(c[t],a[t]);
            for(int i=0;i<g[t].size();i++)
            if(c[t]<c[g[t][i]])
            c[g[t][i]]=c[t],q.push(g[t][i]);
        }
        q.push(n);
        while(!q.empty())
        {
            int t=q.front();
            q.pop();
            b[t]=max(b[t],a[t]);
            for(int i=0;i<s[t].size();i++)
            if(b[t]>b[s[t][i]])
            b[s[t][i]]=b[t],q.push(s[t][i]);
        }
        int ans=0;
        for(int i=1;i<=n;i++)
        ans=max(ans,b[i]-c[i]);
        cout<<ans;
        return 0;
    }

    20:12:48 所以暂时将你眼睛闭了起来   ——伍佰

    多源最短路径(任意两点间最短路径)

    • Floyd算法

    状态转移方程:d[i][j]=min(d[i][j],d[i][k]+d[k][j]);

    可用邻接矩阵存图

    题目描述

    输入一个有向图的邻接矩阵格式,输出每对顶点间的最短路径长度

    输入

    输入第一行为n,表示下面是个n n的矩阵,接下来就是n n的矩阵形式,每个元素值都是整型,如果不能直接到,则是-1

    输出

    输出也是一个n * n的矩阵形式,每个元素的值为两个顶点间的最短路径值,如果到不了,则输出-1,最后一行的换行也输出

    输入样例

    3
    0 1 3
    2 0 5
    3 2 0

    输出样例

    0 1 3 2 0 5 3 2 0
    这是一道模板题,所以直接看代码。

    #include<bits/stdc++.h>
    using namespace std;
    int a[60][60];
    int x,y,n;
    int main()
    {
        cin>>n;
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            cin>>a[i][j];
            if(a[i][j]==-1)
            a[i][j]=9999999;
        }
        
        for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(a[i][j]==9999999)
                a[i][j]=-1;
                cout<<a[i][j]<<" ";
            }
            cout<<endl;
        }
        return 0;
    }

    文化之旅

    因为Floyd是插点找最短路,所以每次状态都会被记录,插的点的文化就会被记录在这条路里,比如若在i,j中插入k时没有矛盾且满足最短路,那么将两条路上的信息合并并更新k的信息,就可以得到新路径的值。只需要用Floyd再加上一部分优化就好,只是要想到用Floyd不容易,可能会想到用Dijkstra算法(可是不知道能不能过)

    #include<bits/stdc++.h>
    using namespace std;
    int n,k,m,s,t,u,v,d,c[102],a[102][102],f[102][102];
    int main()
    {
        cin>>n>>k>>m>>s>>t;
        for(int i=1;i<=n;i++)
        cin>>c[i];
        for(int i=1;i<=k;i++)
        for(int j=1;j<=k;j++)
        cin>>a[i][j];
        memset(f,60,sizeof(f));
        for(int i=1;i<=m;i++)
        {
            cin>>u>>v>>d;
            if(!a[c[v]][c[u]]&&c[v]!=c[u])f[u][v]=min(f[u][v],d);
            if(!a[c[u]][c[v]]&&c[u]!=c[v])f[v][u]=min(f[v][u],d);
        }
        if(c[s]==c[t])
        {
            cout<<-1;
            return 0;
        }
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(i!=j)
            for(int k=1;k<=n;k++)
            {
                if(j!=k&&i!=k)
                f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
            }
        }
        if(f[s][t]>999999)cout<<-1;
        else cout<<f[s][t];
        return 0;
     } 

    20:52:02 

    我终于要认认真真看电视了!!!

  • 相关阅读:
    Python学习笔记(2)
    Python学习笔记(1)
    2020年5月记于博茨瓦纳
    20145208 蔡野 《网络对抗》免考项目 MSF学习
    20145208 蔡野 《网络对抗》Exp9 web安全基础实践
    20145208 蔡野 《网络对抗》Exp8 Web基础
    20145208 蔡野 《网络对抗》Exp7 网络欺诈技术防范
    20145122 《Java程序设计》课程总结
    20145122《 Java网络编程》实验五实验报告
    20145122 《Java程序设计》第十周学习总结
  • 原文地址:https://www.cnblogs.com/wybxz/p/12214575.html
Copyright © 2011-2022 走看看