1. 程式人生 > 實用技巧 >最短路演算法總結

最短路演算法總結

樸素版Dijkstra(適用於 無負權變的稠密圖)


#include <iostream>
#include <cstring>

using namespace std;
const int N = 510;
int n, m;
int dis[N], g[N][N];  // dis陣列存放起點到各點之間的最短距離
bool st[N];  // st陣列判斷這個點是否已經加入了最短路徑的集合

int Dijkstra(int start, int end){
    memset(dis, 0x3f, sizeof dis);
    dis[start] = 0;  // 起點到起點的距離為0,同時不能吧st[起點]變成1, 不然不會更新與起點相鄰的點
    for(int i = 0; i < n; ++i){
        int t = -1;
        // 在未加入最短路集合的點中找到距離起點最小的點
        for(int j = 1; j <= n; ++j){
            if(!st[j] && (t == -1 || dis[t] > dis[j]))
                t = j;
        }
        //將找到的點加入集合
            
        st[t] = true;

        // 由這個點來更新起點到其他點的距離
        for(int j = 1; j <= n; ++j){
            dis[j] = min(dis[j], dis[t] + g[t][j]);
        }
    }
    
    return dis[end];
}

int main(){
    memset(g, 0x3f, sizeof g);
    scanf("%d%d", &n, &m);
    
    while(m --){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        g[a][b] = min(g[a][b], c);
    }
    
    int ans = Dijkstra(1, n);
    if(ans == 0x3f3f3f3f) printf("-1");
    else printf("%d", ans);
    return 0;
}

Bellman-Ford演算法時間輔助度O(n*m) 幾乎不會使用,除了少數情況如這題,要求最多k條邊的最短路徑,適用於稀疏圖,可以處理負權邊,同時可以判斷是否存在負環,但是一般不用他來判斷負環,用佇列優化版的Bellman-Ford也就是spfa演算法來判斷負環。

#include <iostream>
#include <cstring>

using namespace std;
const int N = 10010, M = 510;
int dist[M], backup[M]; // backup 為dist的拷貝陣列
// 對建邊沒有條件,所以直接用一個結構體陣列來儲存
struct Edge{
    int a, b, v;
}edges[N];
int n, m, k;

// 全名Bellman_ford演算法,幾乎不用,除了這道題,因為k表示的最多經過k條邊的最短路徑當
int bf(int start, int end){
    memset(dist, 0x3f, sizeof dist);
    dist[start] = 0;
    for(int i = 0; i < k; ++i){
        memcpy(backup, dist, sizeof dist);  // 為了讓每次在下面for迴圈中dist不被反覆更新,我們讓backup為上一次迴圈的dist陣列
        for(int j = 0; j < m; ++j){
            auto t = edges[j];
            dist[t.b] = min(dist[t.b], backup[t.a] + t.v);  // 用上一次的dist陣列來更新
        }
    }
    return dist[end];
}

int main(){
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 0; i < m; ++i){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        edges[i] = {a, b, c};
    }
    
    int ans = bf(1, n);
    if(ans > 0x3f3f3f3f / 2) puts("impossible");  // 因為可能存在負權變所以正無窮的邊也可能被更新
    else printf("%d", ans);
    return 0;
}

佇列優化版Bellman-Ford也叫Spfa,適用於稀疏圖,可處理負權邊以及判斷負環,時間複雜度O(m),但有一種將時間複雜度卡到O(n*m)的方法這時就有可能超時,但一般不會卡

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;
const int N = 100010;
int h[N], ne[N], e[N], w[N], idx;
int n, m;
int dis[N], st[N];
queue<int> q;

// 鏈式前向星核心程式碼
void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

void spfa(int start){
    memset(dis, 0x3f, sizeof dis);  // 初始化
    q.push(start);  // 將起點放入佇列
    st[start] = true;  // 在佇列中的點為true,否則為false
    dis[start] = 0;
    
    while(q.size()){
        auto t = q.front();  
        q.pop();
        st[t] = false;
        // 遍歷出隊的臨邊
        for(int i = h[t]; ~i; i = ne[i]){
            int j = e[i];
            // 如果可以更新,並且佇列中沒有,那麼就將這個點也加入隊列當中
            if(dis[j] > dis[t] + w[i]){
                dis[j] = dis[t] + w[i];
                if(!st[j]){
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
}

int main(){
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    while(m --){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }
    
    spfa(1);
    
    if(dis[n] == 0x3f3f3f3f) puts("impossible");
    else printf("%d", dis[n]);
    return 0;
}

spfa判斷是否存在負環

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int N = 100010;
int h[N], e[N], ne[N], idx, w[N], dis[N], cnt[N];
bool st[N];
int n, m;
queue<int> q;

void add(int a, int b, int c){
    w[idx] = c, e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// 當一個點被入隊的次數大於能於n次則肯定存在負環,cnt陣列表示到達當前這個點最短路的邊數,
//如果cnt[x]>=n,說明至少經過了n條邊,即n+1個點,由抽屜原理可知顯然有兩個點重複,即存在負環
bool spfa(){
    
    for(int i = 1; i <= n; ++ i){
        q.push(i);
        st[i] = i;
    }
    while(q.size()){
        auto t = q.front();
        q.pop();
        st[t] = false;
        for(int i = h[t]; ~i; i = ne[i]){
            int j = e[i];
            if(dis[j] > dis[t] + w[i]){
                dis[j] = dis[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if(cnt[j] >= n) return true;
                if(!st[j]){
                    q.push(j);
                    st[j] = true;
                }
            }
            
        }
    }
    return false;
}

int main(){
    scanf("%d%d", &n, &m); 
    memset(h, -1, sizeof h);
    for(int i = 0; i < m; ++i){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }
    if(spfa()) puts("Yes");
    else puts("No");
    return 0;
}