bellman-ford 單源最短路問題 圖解

self_disc發表於2022-04-27

核心思想:鬆弛操作

對於邊(u,v),用dist(u)和(u,v)的和嘗試更新dist(v):

                        dist(v) = min(dist(v) , dist(u)+l(u,v)

注:dist(i)為源點(起點)到i點的距離,l(u,v)為u->v的邊權。

Bellman-Ford的基本操作是進行多次迭代,每一輪迭代對圖上所有邊進行鬆弛操作,直到再一次迭代中沒有點的dist發生變化即可停止迭代。為什麼呢?不妨假設已經沒有dist發生變化了,再進行一輪迭代的話,很顯然,之後的迭代沒有產生任何作用,dist陣列依舊沒有改變,反倒增大了時間複雜度,這不是多此一舉麼。

圖解:

                                                        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​bellman-ford 單源最短路問題 圖解

初始:(S為源點)

初始設定為inf無窮大,表示還沒有最短路

S A B C D E
0 inf inf inf inf inf

第一輪迭代:

對S點連出的邊(s->e,s->a)

S A B C D E
0 7(0+7) inf inf inf 5(0+5)

 對A連出的邊(a->c)

S A B C D E
0 7 inf 9(7+2) inf 5

對B連出的邊(b->a)

S A B C D E
0 7 inf 9 inf 5

               dist(B)還沒有找到最短路,更新其他點的最短路徑無意義,故對B點的出邊不進行鬆弛

對C連出的邊(c->b)

S A B C D E
0 7 7(9+(-2)) 9 inf 5

對D連出的邊(d->c,d->a)

S A B C D E
0 7 7 9 inf 5

               dist(D)還沒有找到最短路,更新其他點的最短路徑無意義,故對D點的出邊不進行鬆弛 

 對E連出的邊(e->d)

S A B C D E
0 7 7 9 6(5+1) 5

已經對所有的邊進行了鬆弛操作,第一輪迭代結束

第二輪迭代

對S點連出的邊(s->e,s->a)

S A B C D E
0 7 7 9 6 5

                                                                            無需更新

 對A連出的邊(a->c)

S A B C D E
0 7 7 9 6 5

                                                                            無需更新 

對B連出的邊(b->a)

S A B C D E
0 7 7 9 6 5

                                                                           無需更新

對C連出的邊(c->b)

S A B C D E
0 7 7 9 6 5

                                                                            無需更新 

對D連出的邊(d->c,d->a)

S A B C D E
0 2(6+(-4)) 7 5(6+(-1)) 6 5

 對E連出的邊(e->d)

S A B C D E
0 2 7 5 6 5

已經對所有的邊進行了鬆弛操作,第二輪迭代結束

第三輪迭代

與第一第二輪同理(此處直接給出迭代結束的結果)

S A B C D E
0 2 2 4 6 5

第四輪迭代

無任何更新,迭代結束,更新完成

 

演算法分析:

如果最短路存在,一定存在一個不含環的最短路。(理由:對零環和正環,去掉後路徑不會邊長;對負環,若最短路徑中存在負環,那一定不是最短路,負環可以無限繞下去,路徑可以是負無窮)

最短路不含環,那麼一條最短路徑最多經過n-1個點(不含起點),所以最多需要n-1輪鬆弛操作。

複雜度分析:

最多進行n-1次迭代,每次迭代列舉遍歷所有邊,嘗試通過邊進行鬆弛操作,故複雜度為

O(N-1)*O(M)即O(NM),(注:N為點數,M為邊數)

虛擬碼

   for (int i = 0; i <= n; i++)

        dist[i] = inf;//初始化為無窮大

    dist[s] = 0;//s為起點,自己到自己的最短路為0

    for (int k = 1; k <= n - 1; k++)//迭代n-1輪

    {

        for (int i = 1; i <= m; i++)//列舉每一條邊

        {

            int x = u[i], y = v[i];

            if (dist[x] < inf)

                dist[y] = min(dist[y], dist[x] + w[i]);//鬆弛

        }

    }

 檢查有無負環

將dist陣列初始化為0,迭代n-1次後進行第n次迭代,如果第n次迭代有進行鬆弛操作,則一定存在負環,因為不存在負環最多隻能進行n-1次鬆弛操作

程式碼實現:

void bellman_ford(int s, int end) // s為起點,end為終點

{

    memset(dis, 127, sizeof(dis));

    dis[s] = 0; //起點最短路為0

    pre[s] = -1;

    for (int i = 1; i <= n - 1; i++)

    {

        bool ok = false;

        for (int j = 1; j <= m; j++)

        {

            int x = edge[j].u, y = edge[j].v, w = edge[j].w;

            if (dis[x] < (1 << 30) && dis[x] + w < dis[y])

            {

                dis[y] = dis[x] + w;

                pre[y] = x; // y的上一個點為x,如不需列印路徑無需pre陣列

                ok = true;

            }

        }

        if (ok == false)

        {

            break; //未進行鬆弛操作,提前退出迴圈,減小時間複雜度

        }

    }

    if (dis[end] < (1 << 30))

        cout << dis[end] << "\n";

    else

        cout << "-1\n";

    // Print_Path(end); //列印路徑

}

模板題 

題目連結:最短路 - 題目 - Daimayuan Online Judge

題目描述:

給你一張簡單有向圖,邊權都為非負整數。以及一些詢問,詢問兩個點之間的距離。

圖用以下形式給出:

第一行輸入三個整數 n,m,k表示圖的頂點數、邊數和詢問次數,頂點編號從 1 到 n。

接下來 m 行,每行三個整數 x,y,z表示 x 到 y 有一條有向邊,邊權為 z。

接下來 k 行,每行兩個整數 x,y 詢問從 x 到 y 的最短路長度,如果無法到達,輸出 −1。

輸入格式:

第一行三個整數 n,m,k 表示圖的頂點數、邊數和詢問次數。

接下來 m 行,每行有三個整數,代表一條邊。

接下來 k 行,每行有兩個整數,代表一次詢問。

輸出格式:

輸出共 k 行,每行一個數表示一次詢問的答案。

資料規模: 

對於所有資料,保證 2≤n≤5000,0≤m≤10000,1≤k≤5,1≤x,y≤n,x≠y,1≤z≤10000。

樣例輸入:

3 3 2

1 2 3

2 3 2

3 2 1

1 3

3 1

樣例輸出:

5

-1 

直接給程式碼了

#include <bits/stdc++.h>
using namespace std;
struct Edge
{
    int u, v, w;
} edge[100009];
int pre[100009];          //記錄上一個點,為了列印最短路徑
int dis[100009], n, m, k; // n為點數,m為邊數,dis[i]為起點到i的最短距離
void Print_Path(int x)
{
    if (pre[x] == -1)
    {
        cout << x; //起點的pre為-1,所以x為起點
        return;
    }
    else
    {
        Print_Path(pre[x]);
        cout << "->" << x;
    }
}
void bellman_ford(int s, int end) // s為起點,end為終點
{
    memset(dis, 127, sizeof(dis));
    dis[s] = 0; //起點最短路為0
    pre[s] = -1;
    for (int i = 1; i <= n - 1; i++)
    {
        bool ok = false;
        for (int j = 1; j <= m; j++)
        {
            int x = edge[j].u, y = edge[j].v, w = edge[j].w;
            if (dis[x] < (1 << 30) && dis[x] + w < dis[y])
            {
                dis[y] = dis[x] + w;
                pre[y] = x; // y的上一個點為x
                ok = true;
            }
        }
        if (ok == false)
        {
            break; //未進行鬆弛操作,提前退出迴圈,減小時間複雜度
        }
    }
    if (dis[end] < (1 << 30))
        cout << dis[end] << "\n";
    else
        cout << "-1\n";
    // Print_Path(end); //列印路徑
}
int main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); //關同步流
    cin >> n >> m >> k;
    for (int i = 1; i <= m; i++) //讀入邊
    {
        cin >> edge[i].u >> edge[i].v >> edge[i].w;
    }
    for (int i = 1; i <= k; i++) // k次詢問
    {
        int x, y;
        cin >> x >> y;
        bellman_ford(x, y);
    }
}
bellman-ford 單源最短路問題 圖解

 參考文獻:

《演算法競賽,入門經典(第二版)》

2022 Namomo Spring Camp Div2 Day8 直播課

ending

有什麼錯誤之處歡迎指正!不勝感激!

 

相關文章