1. 程式人生 > 其它 >853. 有邊數限制的最短路

853. 有邊數限制的最短路

題目傳送門

Bellman-Ford演算法

演算法思路:

for n次
for 所有邊 a,b,w 表示存在一條a->b 的邊,權重是 w
dist[b]=min(dist[b],dist[a]+w)

結束!

概念:瞭解即可
對於所有的邊都滿足 dist[b]<=dist[a]+w ,這個叫三角不等式,更新操作叫鬆弛操作。

Bellman_Ford適用於負權邊的,

有負權迴路的話,最短路是不一定存在的。為什麼是不一定,可以看圖1和圖2.
圖1描述了一直走下去一直小,那麼,最短路徑就是負無窮,不存在最短路徑。
圖2描述了雖然有負權迴路,但由於此負權迴路不在1~n的路線中,不影響最短路,所以,也還存在最短路徑。

Bellman_Ford是可以找負環的,但時間複雜度比較高,一般不用它來找負環。

Bellman_Ford能幹的事,SPFA都能幹,而且時間複雜度比Bellman_Ford要低。
但有一個例外,就是如果限制了邊數的最短路,那麼只能用Bellman_Ford演算法。

演算法邏輯

1)初始化所有點到源點的距離為∞,把源點到自己的距離設定為0;

2)不管3721遍歷n次;每次遍歷m條邊,用每一條邊去更新各點到源點的距離。

值得注意的是

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

  2. ⭐️由於存在負權邊,因此return -1的條件就要改成dist[n]>0x3f3f3f3f/2;

  3. ⭐️上面所謂的n次遍歷的實際含義是當前的最短路徑最多有n-1條邊,這也就解釋了為啥要i遍歷到n的時候退出迴圈了,因為只有n個點,最短路徑無環最多就存在n-1條邊。

  4. ⭐️這裡無需對重邊和自環做單獨的處理:
    1] 重邊:由於遍歷了所有的邊,總會遍歷到較短的那一條; 2] 自環: 有自環就有自環啊,反正又不會死迴圈;

  5. ⭐️令人愉悅的是,該演算法無非就是迴圈n次然後遍歷所有的邊,因此不需要做什麼特別的儲存,只要把所有的邊的資訊存下來能夠遍歷就行;

6)⭐️bellman_ford演算法可以存在負權迴路,因為它求得的最短路是有限制的,是限制了邊數的,這樣不會永久的走下去,會得到一個解;

  1. ⭐️SPFA演算法各方面優於該演算法,但是在碰到限制了最短路徑上邊的長度時就只能用bellman_ford了,此時直接把n重迴圈改成k次迴圈即可

理解與感悟

(1)本題明確存在負權邊,所以不能使用Dijkstra演算法,Dijkstra只能用在正權邊。

(2)有邊數限制的最短路徑
bellman - ford演算法擅長解決有邊數限制的最短路問題
這個有邊數限制就很霸氣了,其它演算法沒有這個功能!
比個現實中的場景:旅遊N個城市,1->n沒有直達的飛機,我們需要經其它的城市進行中轉,
票價就是中轉的機票票價之和,每次中轉都會讓人的心情不爽一點,也就是不能超過k次中轉。

(3)Bellman_ford是可以用來判斷負環的,但由於時間複雜度較高,一般使用SPFA來判斷負環。

(4)什麼情況下存在負環,還存在最短路徑?
比如
上面這張圖之所以不存在負環,是因為2號點不在1->n點的路徑上,就是有負環,也無謂!

(5)、為什麼需要backup[a]陣列
為了避免如下的串聯情況, 在邊數限制為一條的情況下,節點3的距離應該是3,但是由於串聯情況,利用本輪更新的節點2更新了節點3的距離,所以現在節點3的距離是2。

正確做法是用上輪節點2更新的距離--無窮大,來更新節點3, 再取最小值,所以節點3離起點的距離是3。

C++ 程式碼

#include <iostream>
#include <cstring>

using namespace std;
const int N = 510, M = 10010;

//bellman_ford演算法可以使用結構體,不用使用鄰接表,很牛B,為什麼Dijkstra不行呢?
struct Edge {
    int a, b, w; //起點,終點和權重
} edges[M];

int n, m, k;
int dist[N];
int backup[N];

int bellman_ford() {
    //初始化
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < k; i++) { //不超過k條邊的最短距離
        //把dist陣列備份一下,只用上一次迭代的結果,防止出現串聯,因為本次迭代可以造成幾個節點資料的修改
        memcpy(backup, dist, sizeof dist);

        for (int j = 0; j < m; j++) {
            auto e = edges[j];
            dist[e.b] = min(dist[e.b], backup[e.a] + e.w);
        }
    }
    return dist[n];
}

int main() {
    //輸入
    scanf("%d%d%d", &n, &m, &k); //不超過k次
    for (int i = 0; i < m; i++) {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        edges[i] = {a, b, w};
    }
    //呼叫bellman_ford演算法 
    int res = bellman_ford();
     //為什麼要用dist[n]>0x3f3f3f3f/2? 這可能是因為在操作過程中dist[n]被更新成0x3f3f3f3f小一點的數,其實還是無法到達
    if (res > 0x3f3f3f3f / 2) puts("impossible");
    else printf("%d\n", res);
    return 0;
}