zoukankan      html  css  js  c++  java
  • 最短路问题(spfa)

    自己是真的笨

    整整用了10个小时才吃透这个BF的两种优化

    题目如下:

    问题描述
    给定一个n个顶点,m条边的有向图(其中某些边权可能为负,但保证没有负环)。请你计算从1号点到其他点的最短路(顶点从1到n编号)。
    
    输入格式
    第一行两个整数n, m。
    
    接下来的m行,每行有三个整数u, v, l,表示u到v有一条长度为l的边。
    
    输出格式
    共n-1行,第i行表示1号点到i+1号点的最短路。
    样例输入
    3 3
    1 2 -1
    2 3 -1
    3 1 2
    样例输出
    -1
    -2
    数据规模与约定
    对于10%的数据,n = 2,m = 2。
    
    对于30%的数据,n <= 5,m <= 10。
    
    对于100%的数据,1 <= n <= 200001 <= m <= 200000,-10000 <= l <= 10000,保证从任意顶点都能到达其他所有顶点。

    很简单的题,就是测试数据n居然有20000;

    那么需要考虑的问题就有两个了,一是时间复杂度,二是空间复杂度

    一开始上手我用的是floyd

    代码如下:

    #include <iostream>
    #define max 2001
    #define inf 99999
    using namespace std;
    int a[max][max]={0}; //邻接矩阵储存图的信息
    int main()
    {
        int m,n,u,v,l;
        cin>>n>>m;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                  a[i][j]=inf;
        for(int i=1;i<=m;i++)
        {
            cin>>u>>v>>l;
            a[u][v]=l;
        }
        for(int k=1;k<=n;k++)  //floyd主体
            for(int i=1;i<=n;i++)
                for(int j=1;j<=n;j++)
                    if(a[i][j]>a[i][k]+a[k][j])
                       a[i][j]=a[i][k]+a[k][j];
         for(int i=2;i<=n;i++)
             cout<<a[1][i]<<endl;
        return 0;
    }

    过了50%的数据,意料之中,两个问题都犯了

    o(n^3)的时间复杂度 20000是不可能的

    二维数组开到20000也是不可能的,内存超出限制,oj显示编译错误

    然后我用了BF,用了一个结构体数组来储存点与点之间的路径

    class road{
        public:
            int start;   //起始点
            int target;  //终点
            int distance;//距离
    };
    road roadque[max];

    n个点需要n-1遍 每遍枚举每一条边  时间复杂度o(nm)

    for(int k=1;k<=n-1;k++)           //进行n-1次松弛 
         for(int i=1;i<=m;i++)           //枚举每一条边 
            if(dis[roadque[i].target]>dis[roadque[i].start]+dis[roadque[i].distance])//尝试松弛每一条边
                dis[roadque[i].target]=dis[roadque[i].start]+dis[roadque[i].distance];

    按题中的数据的话是40亿次运算,emmmmm,TLE,GG 

    然后我去学了SPFA

    队列优化了一遍,代码如下:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    using namespace std;
    
    const int N = 20001;       
    const int INF = 99999999;//理解成正无穷
    
    int map[N][N], dist[N]; //map邻接矩阵,dis当前点到其他点的距离数组
    bool book[N]; //判断是否已经在队列中
    int n, m;
    
    void init()
    {//初始化
        int i, j;
        for (i = 1; i <= N; i++)
            for (j = 1; j <= N; j++)
            {
                if (i == j)
                    map[i][j] = 0;
                else
                    map[i][j] = INF;
            }
    }
    
    void spfa(int start)
    {
        queue<int> Q;
        int i, now;
        memset(book, false, sizeof(book));
        for (i = 1; i <= n; i++)
            dist[i] = INF;
        dist[start] = 0; //到自己的距离为0
        Q.push(start);  //把自己加入到队列中
        book[start] = true;//不可以入队
        while (!Q.empty()) 
        {
            now = Q.front();
            Q.pop();
            book[now] = false;
            for (i = 1; i <= n; i++)
                if (dist[i] > dist[now] + map[now][i])
                {
                    dist[i] = dist[now] + map[now][i];
                    if (book[i] == 0)
                    {
                        Q.push(i); //如果松弛成功且当前点不在队列中,则加入到队列中
                        book[i] = true;//置为不可以入队状态
                    }
                }
        }
    }
    
    int main()
    {
        scanf("%d%d", &n, &m);
        init();
        for (int i = 0; i < m; i++)
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            if (map[a][b] > c)
                map[a][b] = c;
        }
    
        spfa(1);
        for (int i = 1; i <= n; i++)
            cout << dist[i] << endl;
    
        return 0;
    }

    时间复杂度看西交大的论文里说大概是o(km) k一般为1-2

    emmmmmm,1-2吗?我信了你的邪,这个图m有20w 

    平均一下20w/2w 差不多每个点进去10次吧 k≈10

    吐血  不过还好,不是最坏的时间复杂度

    嗯,那么到这里超时的问题就解决了

    但是!

    邻接矩阵要开到2w明显不可能

    所以又一次编译错误,

    然后我去学了邻接表

    数组方法的邻接表太巧妙了,老实人用不了,溜了

    这里使用stl里的list来实现邻接表

    对于这样的数据

    4 5

    1 4 9
    4 3 8
    1 2 5
    2 4 6
    1 3 7

    这是示意图

    #include <iostream>
    #include <list>
    #include <queue>
    #include <cstring>
    #define max 20001
    const int inf = 1<<29; 
    int sfpa(int v0);
    using namespace std;
    class road {
    public:
        int target;
        int distance;
    };
    
    list<road> roadlist[max]; //邻接表
    int dis[max]; //距离数组
    int n, m;
    int main()
    {
        cin >> n >> m;
        int a, b, c;
        for (int i = 0; i<m; i++)//录入邻接表
        {
            cin >> a >> b >> c;
            road ex;
            ex.target = b;
            ex.distance = c;
            roadlist[a].push_front(ex);
        }
        sfpa(1);
        for (int i = 2; i <= n; i++)
            cout << dis[i] << endl;
        return 0;
    }
    int sfpa(int v0)
    {
        queue<int> que;
        int book[max]; //判断是否在队列中
        memset(book, 0, sizeof(book));
        for (int i = 1; i <= n; i++)//初始化
            dis[i] = inf;
        dis[v0] = 0;
        que.push(v0);
        book[v0] = 1;
        while (!que.empty())
        {
            int now = que.front();
            que.pop();
            book[now] = 0;
            list<road>::iterator it = roadlist[now].begin();
            for (it; it != roadlist[now].end(); it++)//遍历领接表
            {
                if (dis[it->target] > dis[now] + (it->distance))
                {
                    dis[it->target] = dis[now] + (it->distance);//更新距离
                    if (book[it->target] == 0)//如果松弛成功且不在队列中,则把该点加入队列
                    {
                        que.push(it->target);
                        book[it->target] = 1;
                    }
                }
            }
        }
        return 0;
    }

     需要注意的是,只有松弛成功才允许把点加到队列中

    到了这里终于ac了

    = =呼

    不知道为啥,用spfa总觉得不安心

    dijkstra又解决不了负权边

    希望蓝桥杯没有卡spfa的题

    卡spfa真的丧心病狂!

  • 相关阅读:
    JavaScript 知识
    Sleep,Hibernate and Hybrid
    CentOS 7 休眠系统
    centos7如何添加开机启动服务/脚本
    linux下执行.sh文件的方法和语法
    systemctl命令
    linux 更新yum源 改成阿里云源
    Supervisor进程管理&开机自启
    Django中的Request和Response
    ORACLE 触发器
  • 原文地址:https://www.cnblogs.com/seamusopen/p/8590286.html
Copyright © 2011-2022 走看看