【算法】 有邊數限制的最短路(bellman - ford算法)

有邊數限制的最短路

題目
給定一個n個點m條邊的有向圖,圖中可能存在重邊和自環, 邊權可能爲負數。
請你求出從1號點到n號點的最多經過k條邊的最短距離,如果無法從1號點走到n號點,輸出impossible。
注意:圖中可能 存在負權迴路 。

輸入格式
第一行包含三個整數n,m,k。
接下來m行,每行包含三個整數x,y,z,表示存在一條從點x到點y的有向邊,邊長爲z。
輸出格式
輸出一個整數,表示從1號點到n號點的最多經過k條邊的最短距離。
如果不存在滿足條件的路徑,則輸出“impossible”。
數據範圍

1≤n,k≤500,
1≤m≤10000,
任意邊長的絕對值不超過10000。
輸入樣例:
3 3 1
1 2 1
2 3 1
1 3 3
輸出樣例:
3

算法分析

1、問題:爲什麼Dijkstra不能使用在含負權的圖中?
分析:
在這裏插入圖片描述
若通過Dijkstra算法可以求出從1號點到達4號點所需的步數爲7。因爲diskstra算法的核心是選擇離源點最短距離的點更新其他點,從圖上可知Dijkstrea算法1號點只能選擇2號點更新其它位置,所以最短的負權變被忽略掉了,所以不可以用Dijkstra算法。
其實1號到4號點的最短距離是-2。

2、什麼是bellman - ford算法?
Bellman - ford算法是求含負權圖的單源最短路徑的一種算法,效率較低,代碼難度較小。其原理爲連續進行鬆弛,在每次鬆弛時把每條邊都更新一下,若在n-1次鬆弛後還能更新,則說明圖中有負環,因此無法得出結果,否則就完成。
(通俗的來講就是:假設1號點到n號點是可達的,每一個點同時向指向的方向出發,更新相鄰的點的最短距離,通過循環n-1次操作,若圖中不存在負環,則1號點一定會到達n號點,若圖中存在負環,則在n-1次鬆弛後一定還會更新)

3、bellman - ford算法的具體步驟

for n次
for 所有邊 a,b,w (鬆弛操作)
dist[b] = min(dist[b],back[a] + w)
注意:back[]數組是上一次迭代後dist[]數組的備份,由於是每個點同時向外出發,因此需要對dist[]數組進行備份,若不進行備份會因此發生串聯效應,影響到下一個點

4、是否能到達n號點的判斷中需要進行if(dist[n] > INF/2)判斷,而並非是if(dist[n] == INF)判斷,原因是INF是一個確定的值,並非真正的無窮大,會隨着其他數值而受到影響,dist[n]大於某個與INF相同數量級的數即可,1到3的距離並不是INF, 所以不能判斷是否=INF
在這裏插入圖片描述
5、bellman - ford算法擅長解決有邊數限制的最短路問題
時間複雜度 O(nm)O(nm)
其中n爲點數,m爲邊數 在這裏是O(km),k個點

AC代碼

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

int n,m,k;


/*
有可能存在負權迴路,最短路不一定不存在了。 有可能是負無窮

for(n)

所有邊 a,b,w

遍歷所有的邊 m條邊

dist[n] = min(dist[b],dist[a]+w);


*/
const int N = 510;
const int M = 10010;

int dist[N], backup[N];

struct Edge
{
  int a,b,w;  
}edges[M];



int bellman_ford()
{
    memset(dist, 0x3f, sizeof dist); //先給 dist上最大值
    dist[1] = 0; //1到1點的最小距離初始化爲0
    
    
    for(int i = 0; i < k; i++)  // 不超過k次
    {
        memcpy(backup,dist, sizeof dist); //每一次將dist數組備份一份,防止出現串聯,更新的時候只用上一次出現的結果
        
        for(int j = 0; j < m; j++){
            int a = edges[j].a;
            int b = edges[j].b;
            int w = edges[j].w;
            
            dist[b] = min(dist[b],backup[a] + w); // backup中存的就是上一次更新的結果
        }
    }
    if(dist[n] > 0x3f3f3f3f/2)  return -1;
        return  dist[n];
}
int main()
{
    scanf("%d%d%d", &n,&m,&k); 
    
    for(int i = 0; i < m; i++)
    {
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w); // m個邊
        edges[i] = {a,b,w};
    }
    
    int t = bellman_ford();  
    if(t == -1)
    {
        puts("impossible");
    }
    else
    {
        printf("%d\n",t);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章