多源最短路模板——hdu1874:暢通工程續(使用dijkstra、bellman-ford、spfa、dijkstra+堆優化)

題目傳送門

hdu1874:暢通工程續


解題思路

這題因爲數據量比較小,可以使用多種最短路算法來解決,是一道經典的模板題,下面附上floyd算法、dijkstra算法、Bellman-Fordspfa算法、以及dijkstra + heap優化的代碼。

坑點:這題可能一個城市到另一個城市有多條路徑,我們記錄的時候,要記錄最小的那條路徑,不能記錄最後的那條路徑,解其他題目的時候也要注意。以及,注意城市的起始點是從0開始算還是1開始算。


Floyd算法

該算法極其暴力,時間複雜度爲O(n3)O(n^3)​,但是算法簡單,容易理解,核心代碼只有4~5行,非常容易理解。

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 205;
int arr[N][N];
int n, m, st, ed;
inline void init()
{
    for(int i = 0; i < n; ++i)
        for(int j = 0; j < n; ++j)
            arr[i][j] = (i == j ? 0 : INF);
}

void Floyd()
{
    for(int k = 0; k < n; ++k)
        for(int i = 0; i < n; ++i)
            for(int j = 0; j < n; ++j) 
                arr[i][j] = min(arr[i][j], arr[i][k] + arr[k][j]);
}

int main()
{
    int a, b, c;
    while(cin >> n >> m) {
        init();
        for(int i = 0; i < m; ++i) {
            cin >> a >> b >> c;
            if(arr[a][b] > c) arr[a][b] = arr[b][a] = c;
        }
        Floyd();
        cin >> st >> ed;
        cout << (arr[st][ed] == INF ? -1 : arr[st][ed]) << endl;
    }
    return 0;
}

Dijsktra算法

這個算法是用來針對無負邊權的單源最短路徑問題的,它比floyd算法高效。 對這裏這個第一個循環i<=n-1的解釋: n個點,1到任何一個點的最短路徑邊數不會超過n-1,因爲就算n個點全部連起來,也只有n-1條邊。

因爲最短路徑是一個不包含迴路的簡單路徑,迴路分爲正權迴路(迴路權值之和爲正)和負權迴路(迴路權值之和爲負)。如果最短路徑中包含正權迴路,那麼去掉這個迴路,一定可以得到更短的路徑;如果最短路徑中包含負權迴路,那麼肯定沒有最短路徑,因爲每多走一次負權迴路就可以得到更短的路徑. 因此最短路徑肯定是一個不包含迴路的最短路徑,即最多包含n-1條邊。

容易證明,Dijkstra算法每一次循環可以確定一個頂點的最短路徑,所以至多循環n-1次即可完成最短路求解。

算法時間複雜度:O(n2)O(n^2)​

注:dijsktra算法無法解決負邊權問題。

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 205;
int arr[N][N];
int dis[N];
bool vis[N];
int n, m, st, ed;
inline void init()
{
    for(int i = 0; i < n; ++i)
        for(int j = 0; j < n; ++j)
            arr[i][j] = (i == j ? 0 : INF);
}

void Dijkstra()
{
    int k = 0;
    for(int i = 0; i < n - 1; ++i) {
        int minv = INF;
        for(int j = 0; j < n; ++j) 
            if(dis[j] < minv && !vis[j]) minv = dis[j], k = j;
        
        vis[k] = true;
        for(int v = 0; v < n; ++v) {
            if(arr[k][v] < INF) {
                dis[v] = min(dis[v], dis[k] + arr[k][v]);
            }
        }
    }
}

int main()
{
    int a, b, c;
    while(cin >> n >> m) {
        init();
        for(int i = 0; i < m; ++i) {
            cin >> a >> b >> c;
            if(arr[a][b] > c)
                arr[a][b] = arr[b][a] = c; 
        }
        cin >> st >> ed;
        //初始化距離數組
        for(int i = 0; i < n; ++i) dis[i] = arr[st][i];
        //初始化vis數組
        memset(vis, false, sizeof vis);
        vis[st] = true;
        Dijkstra();
        cout << (dis[ed] == INF ? - 1 : dis[ed]) << endl;
    }
    return 0;
}

Dijsktra + heap 優化

使用優先隊列,每次找尋最小距離的點,這裏有一個技巧,因爲greater對pair的比較,是先比較第一個的,所以把dis[i]放在第一位。

這裏如果使用鄰接表+優先隊列來存儲,可以將時間複雜度優化至O(m+n)log(n)O(m + n) log(n),該算法時間複雜度小於O(n2)O(n^2)

當存在大量邊的情況下,可以考慮使用鄰接表存圖。

//鄰接矩陣寫法
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 205;
typedef pair<int, int> P;
int arr[N][N];
int dis[N];
bool vis[N];
int n, m, st, ed;

inline void init()
{
    memset(dis, INF, sizeof dis);
    memset(vis, false, sizeof vis);
    for(int i = 0; i < n; ++i)
        for(int j = 0; j < n; ++j)
            arr[i][j] = (i == j ? 0 : INF);
}

void Dijkstra()
{
    dis[st] = 0;
    priority_queue<P, vector<P>, greater<P> > q;
    q.push({0, st});
    while(q.size()) {
        P now = q.top(); q.pop();
        int vi = now.second;
        if(vis[vi]) continue;
        vis[vi] = true;
        for(int i = 0; i < n; ++i) {
            if(!vis[i] && dis[i] > dis[vi] + arr[vi][i]) {
                dis[i] = dis[vi] + arr[vi][i];
                q.push({dis[i], i});
            }
            
        }
    }
}

int main()
{
    int a, b, c;
    while(cin >> n >> m) {
        init();
        for(int i = 0; i < m; ++i) {
            cin >> a >> b >> c;
            arr[a][b] = arr[b][a] = min(arr[a][b], c);
        }
        cin >> st >> ed;
        Dijkstra();
        cout << (dis[ed] == INF ? -1 : dis[ed]) << endl;
    }
    return 0;
}
//鄰接表寫法
#include <bits/stdc++.h>
#define PUSH(x,y,z) G[x].push_back({y,z})  // 宏函數
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1005;
typedef pair<int, int> P;
int n, m, st, ed;
vector<P> G[N];
int dis[N];
bool vis[N];

inline void init()
{
    for(int i = 0; i < N; ++i) G[i].clear();
    memset(dis, INF, sizeof dis);
    memset(vis, false, sizeof vis);
}


void Dijkstra()
{
    dis[st] = 0;
    priority_queue<P, vector<P>, greater<P> > q;
    q.push({0, st});
    while(q.size()) {
        P now = q.top(); q.pop();
        int u = now.second;
        if(vis[u]) continue;
        vis[u] = true;
        for(int i = 0; i < G[u].size(); ++i) {
            int v = G[u][i].first;
            int cost = G[u][i].second;
            if(!vis[v] && dis[v] > dis[u] + cost) {
                dis[v] = dis[u] + cost;
                q.push({dis[v], v});
            }
        }
    }
}


int main()
{
    int a, b, c;
    while(cin >> n >> m) {
        init();
        for(int i = 0; i < m; ++i) {
            cin >> a >> b >> c;
            PUSH(a, b, c);
            PUSH(b, a, c);
        }
        cin >> st >> ed;
        Dijkstra();
        cout << (dis[ed] == INF ? -1 : dis[ed]) << endl;
    }
    return 0;
}

Bellman-flod算法

Djikstra算法無法解決負權邊問題,但是這個算法可以解決,並且它的核心語句只有四行,堪稱完美。 這裏同理,循環n-1次,因爲n個點最多的邊數是n-1條。 因爲這個算法可以檢測是否存在負權邊,如果存在負權邊,則無最短路徑,若未出現負權邊,則得出結果

我們增加一個pro數組來記錄上一次的數據,如果更新後數據未變,則說明所有點的最短路徑已經求得,提前結束,可提高效率。

負權檢測,若再進行一次鬆弛,數組改變了,則說明這個圖一定含負邊權。

注: 一般使用該算法來解決存在負邊權的題目。

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1005;
int n, m, st, ed;
int pro[N], dis[N], u[N], v[N], w[N];  //u數組爲起點,v數組爲終點,w數組爲邊權,每一個組合表示一條邊
bool vis[N]; //判斷是否訪問過該點

void Bellman_Ford()
{

    for(int k = 0; k < n - 1; ++k) {
        for(int i = 0; i < n; ++i) pro[i] = dis[i];
        for(int i = 0; i < m; ++i) {
            dis[v[i]] = min(dis[v[i]], dis[u[i]] + w[i]);
            dis[u[i]] = min(dis[u[i]], dis[v[i]] + w[i]);  //無向圖需要反向做一次鬆弛
        }
        bool check = false;
        //鬆弛完畢檢測dis數組是否有更新,如果沒有更新,則可以判斷更新完畢,提高效率
        for(int i = 0; i < n; ++i) 
            if(pro[i] != dis[i]) {
                check = true;
                break;
            }
        if(check) continue;
    }
    bool flag = false;
    for(int i = 0; i < m; ++i) 
        if(dis[v[i]] > dis[u[i]] + w[i]) {
            flag = true;
            break;
        }
    if(flag) cout << "該圖含有負權迴路" << endl;
}

int main()
{
    while(cin >> n >> m) {
        for(int i = 0; i < m; ++i) cin >> u[i] >> v[i] >> w[i];
        memset(dis, INF, sizeof dis);
        cin >> st >> ed;
        dis[st] = 0;
        Bellman_Ford();
        cout << (dis[ed] == INF ? -1 : dis[ed]) << endl;
    }
    return 0;
}

Bellman-flod算法+隊列優化(spfa算法)

這個算法利用BFS思路,還能檢測負權邊,堪稱萬能算法,時間複雜度還能接受。 下面給出兩種寫法:鄰接表的寫法和鄰接矩陣的寫法。這個算法跟BFS算法不一樣的地方就是,BFS一旦出隊的點就不在入隊了,而spfa算法,出隊的點還有可能入隊,因爲可能起點到該點的估計值再次變小。

//鄰接矩陣寫法
#include <bits/stdc++.h>
#define PUSH(x,y,z) G[x].push_back({y,z})  // 宏函數
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1005;
typedef pair<int, int> P;
int n, m, st, ed;
int dis[N], arr[N][N];
bool vis[N];

void init()
{
    for(int i = 0; i < n; ++i)
        for(int j = 0; j < n; ++j)
            arr[i][j] = i == j ? 0 : INF;
    memset(dis, INF, sizeof dis);
    memset(vis, false, sizeof vis);
}

void spfa()
{
    queue<int> q;
    vis[st] = true, dis[st] = 0;
    q.push(st);
    while(q.size()) {
        int now = q.front();
        q.pop(); vis[now] = false;
        for(int i = 0; i < n; ++i) {
            if(dis[i] > dis[now] + arr[now][i]) {
                dis[i] = dis[now] + arr[now][i];
                if(!vis[i]) {
                    vis[i] = true;
                    q.push(i);
                }
            }
        }
    }

}

int main()
{
    int a, b, c;
    while(cin >> n >> m) {
        init();
        for(int i = 0; i < m; ++i) {
            cin >> a >> b >> c;
            arr[a][b] = arr[b][a] = min(arr[a][b], c);
        }
        cin >> st >> ed;
        spfa();
        cout << (dis[ed] == INF ? -1 : dis[ed]) << endl;
    }
    return 0;
}
//鄰接表寫法
#include <bits/stdc++.h>
#define PUSH(x,y,z) vec[x].push_back({y,z})  // 宏函數
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1005;
typedef pair<int, int> P;
int n, m, st, ed;
vector<P> vec[N];
int dis[N];  // 起點到終點的距離
bool vis[N]; // 判斷是否在隊列裏面

void init()
{
    for(int i = 0; i < N; ++i) vec[i].clear();
    memset(dis, INF, sizeof dis);
    memset(vis, false, sizeof vis);
}


void Spfa()
{
    queue<int> q;
    q.push(st); dis[st] = 0, vis[st] = true;
    while(q.size()) {
        int now = q.front(); 
        q.pop(); vis[now] = false;
        for(int i = 0; i < vec[now].size(); ++i) {
            int v = vec[now][i].first;
            if(dis[v] > dis[now] + vec[now][i].second) {
                dis[v] = dis[now] + vec[now][i].second;
                if(!vis[v]) {
                    vis[v] = true;
                    q.push(v);
                }
            }
        }
        
    }
}

int main()
{
    int a, b, c;
    while(cin >> n >> m) {
        init();
        for(int i = 0; i < m; ++i) {
            cin >> a >> b >> c;
            PUSH(a, b, c);
            PUSH(b, a, c);
        }
        cin >> st >> ed;
        Spfa();
        cout << (dis[ed] == INF ? -1 : dis[ed]) << endl;
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章