首頁 > 軟體

C++圖論之Bellman-Ford演演算法和SPFA演演算法的實現

2022-06-14 18:00:37

給定一張有向圖,若對於圖中的某一條邊(x,y,z),有dist[y]≤dist[x]+z成立,則稱該邊滿足三角形不等式。如果所有邊都滿足三角形不等式,則dist陣列就是所求的最短路。

Bellman-Ford演演算法

(x,y,z)表示的是一條從 x 出發, 到達 y ,長度為 z 的有向邊。

首先介紹基於迭代的Bellman-Ford演演算法,它的流程如下:

1.掃描所有邊(x,y,z),若dist[y]>dist[x]+z, 則用dist[x]+z更新dist[y]

2.重複上述操作,直到沒有更新操作發生。

Bellman-Ford演演算法的時間複雜度是O(nm)

通過Bellman-Ford演演算法我們可以求解有邊數限制的最短路問題。

例題:AcWing 853. 有邊數限制的最短路

演演算法步驟

初始化 dist 陣列為正無窮, dist[1] = 0

(外重回圈)迴圈 i 從 1 到 n ,遍歷 n 次表示:是不經過超過 i 條邊到達終點的最短距離

(內重回圈)迴圈 i 從 1 到 m, 遍歷 m 條邊,把所有的邊都進行鬆弛操作:

每次取出兩點以及以及連線他們的權重 (a,b,w)

用以下公式更新最短距離: dist[b]=min(dist[b],dist[a]+w)

注意點:

需要把dist陣列進行一個備份,這樣防止每次更新的時候出現串聯

由於存在負權邊,所以 return -1 的條件是dist[n]>0x3f3f3f/2

程式碼實現

#include <iostream>
#include <cstring>
using namespace std;
 
const int N = 510, M = 10010;
 
struct Edge
{
    int a, b, w;
}e[M]; // 存下每一條即可
int dist[N];
int back[N]; // 備份陣列放置串聯
int n, m, k;
 
void bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    
    for(int i = 0; i < k; i ++ ) // 不超過k條邊
    {
        memcpy(back, dist, sizeof back);
        for(int j = 0; j < m; j ++ ) // 遍歷所有邊
        {
            int a = e[j].a, b = e[j].b, w = e[j].w;
            dist[b] = min(dist[b], back[a] + w);
        }
    }
}
 
int main()
{
    cin >> n >> m >> k;
    for(int i = 0; i < m; i ++ )
    {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        e[i] = {a, b, w};
    }
    
    bellman_ford();
    if(dist[n] > 0x3f3f3f3f / 2) puts("impossible");
    else cout << dist[n] << endl;
    
    return 0;
}

SPFA演演算法

SPFA演演算法在國際上通稱為“佇列優化的“Bellman-Ford演演算法”。

SPFA演演算法的流程如下:

1.建立一個佇列,起初佇列中只含有起點1

2.取出頭結點 x ,掃描它的所有出邊(x,y,z),若dist[y]>dist[x]+z,則使dist[y]用dist[x]+z來更新。同時若y不再佇列中,則將y入隊

在任意時刻,該演演算法的佇列都保持了該拓展的節點。每次入隊都相當於完成了一次 dist 陣列的更新操作,使其滿足三角不等式。一個節點可能會入隊、出隊多次。最終,圖中所有的結點全部收斂到全部滿足三角不等式的狀態。

這個佇列避免了對Bellman-Ford演演算法中不需要拓展的多餘結點的冗餘掃描,在隨機圖上的執行效率O(km)級別,其中 k 是一個很小的常數。

程式碼實現

SPFA求最短路

#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
 
using namespace std;
 
const int N = 1e6 + 10;
 
int n, m;
int h[N], e[N], w[N], ne[N], idx;
int dist[N];
bool st[N];
 
void add(int a, int b, int c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++ ;
}
 
void spfa()
{
    memset(dist, 0x3f, sizeof dist);
    queue<int> q;
    dist[1] = 0;
    st[1] = true;
    q.push(1);
    
    while(q.size())
    {
        int t = q.front();
        q.pop();
        
        st[t] = false;
        
        for(int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[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();
    
    if(dist[n] == 0x3f3f3f3f) puts("impossible");
    else printf("%d",dist[n]);
 
    return 0;
}
 

以上就是C++圖論之Bellman-Ford演演算法和SPFA演演算法的實現的詳細內容,更多關於C++ Bellman-Ford SPFA演演算法的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com