算法训练 最短路


算法训练 最短路  
时间限制:1.0s   内存限制:256.0MB
       
问题描述

给定一个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 <= 20000,1 <= m <= 200000,-10000 <= l <= 10000,保证从任意顶点都能到达其他所有顶点。


思路分析:首先要知道最短路常用的几种算法Dijkstra、Floyd、Bellman-Ford、SPFA、BFS。观察数据范围,顶点数最多有2W,用邻接矩阵存储图会内存超限,导致提交显示运行错误(其实就是内存炸了)。所以要用邻接表存储。因为图中有负权,Dijkstra不能用;因为图是带权的,所以BFS不能用;剩下的三个,好像都可以AC,Floyd时间复杂度较高,是立方级的,不建议用;Bellman-Ford时间复杂度也较高,有待考虑;最为稳妥的做法是用SPFA,SPFA就是利用队列优化过的Bellman-Ford。


先上一个Dijkstra的代码,可以通过80%数据,其余超时,利用小根堆优化理论应该可以AC(我有点疑问...不是Dijkstra不能处理带负权的图吗?还是不能处理带负环的图?)

#include 
#include 
#include 

#define MAX 20000 + 10
#define INF 0x3fffffff

using namespace std;

typedef struct {
    int v;
    int l;
} Edge;

vector MGraph[MAX];
int dist[MAX];
int visit[MAX];

void Dijkstra( int n ) {
    fill( dist, dist + MAX, INF );
    dist[0] = 0;

    for( int i = 0; i < n; i++ ) {
        // 这一部分是找最小值的过程,可以用小根堆优化
        
        int u = -1, MIN = INF;
        for( int j = 0; j < n; j++ ) {
            if( !visit[j] && dist[j] < MIN ) {
                MIN = dist[j];
                u = j;
            }
        }
        
        //
        
        if( u == -1 ) return;
        visit[u] = 1;

        for( int j = 0; j < MGraph[u].size(); j++ ) {
            int v = MGraph[u][j].v;
            if( !visit[v] ) {
                if( MGraph[u][j].l + dist[u] < dist[v] ) {
                    dist[v] = dist[u] + MGraph[u][j].l;
                }
            }
        }
    }
}

int main() {
    int n, m;
    scanf( "%d%d", &n, &m );

    int a, b, l;
    for( int i = 0; i < m; i++ ) {
        scanf( "%d%d%d", &a, &b, &l );
        a--;
        b--;

        Edge e;
        e.v = b;
        e.l = l;
        MGraph[a].push_back( e );
    }

    Dijkstra( n );

    for( int i = 1; i < n; i++ ) {
        printf( "%d\n", dist[i] );
    }

    return 0;
}


下面这个是AC的SPFA算法,时间大约是400MS;我试了下用优先队列,需要600MS

#include 
#include 
#include 
#include 
#include 

#define MAX 20000 + 10
#define INF 0x3fffffff

using namespace std;

typedef struct {
    int v;
    int l;
} Edge;

vector MGraph[MAX];
int dist[MAX];
int visit[MAX];
int inq[MAX];
int num[MAX];

bool SPFA( int s, int n ) {
    // 初始化部分
    memset( inq, false, sizeof( inq ) );
    memset( num, 0, sizeof( num ) );
    fill( dist, dist + MAX, INF );

    // 源点入队部分
    queue Q;
    Q.push( s );    // 源点入队
    inq[s] = true;  // 源点已入队
    num[s]++;       // 源点入队次数加1
    dist[s] = 0;

    // 主体部分
    while( !Q.empty() ) {
        int u = Q.front();  // 队首顶点编号为u
        Q.pop();
        inq[u] = false;     // 设置u不在队列中
        // 遍历u的所有邻接边v
        for( int j = 0; j < MGraph[u].size(); j++ ) {
            int v = MGraph[u][j].v;
            int dis = MGraph[u][j].l;
            // 松弛操作
            if( dist[u] + dis < dist[v] ) {
                dist[v] = dist[u] + dis;
                if( !inq[v] ) { // 如果v不在队列中
                    Q.push( v );    // v入队
                    inq[v] = true;  // 设置v为在队列中
                    num[v]++;       // v的入队次数加1
                    if( num[v] >= n ) return false; // 有可达负环
                }
            }
        }
    }

    return true;    // 无可达负环
}

int main() {
    int n, m;
    scanf( "%d%d", &n, &m );

    int a, b, l;
    for( int i = 0; i < m; i++ ) {
        scanf( "%d%d%d", &a, &b, &l );
        a--;
        b--;

        Edge e;
        e.v = b;
        e.l = l;
        MGraph[a].push_back( e );
    }

    SPFA( 0, n );

    for( int i = 1; i < n; i++ ) {
        printf( "%d\n", dist[i] );
    }

    return 0;
}


你可能感兴趣的:(----最短路,蓝桥杯)