1. 程式人生 > >最短路問題(spfa)

最短路問題(spfa)

i++ 最短路 scanf out 圖片 格式 http 當前 一次

自己是真的笨

整整用了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真的喪心病狂!

最短路問題(spfa)