Dijkstra 算法雖然好,但是他不能解決帶有負權邊的(邊的權值爲負數)的圖,下面我們就來說一下幾乎完妹求最短路徑的算法Bellman-ford。Bellman-ford算法也非常簡單,核心代碼只有幾行,並且可以完美的解決帶有負權的圖,先來看看這個核心代碼吧
for(int k = 1 ; k <= n - 1 ; k ++)
{
for(int i = 1 ; i < m ; i ++)
{
if(dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i] ;
}
}
上邊的代碼外循環共循環了n - 1 次(n爲頂點的個數),內循環共循環了m次(m代表邊的個數)即枚舉每一條邊, dis 數組是的作用和dijkstra 算法一樣,也是用來記錄源點到其餘各個頂點的最短路徑,u,v,w 三個數組用來記錄邊的信息。例如第i條邊存儲在u[i]、v[i]、w[i]中,表示從頂點u[i]到頂點v[i]這條邊(u[i] --> v[i])權值爲w[i]。
兩層for循環的意思是:看看能否通過u[i]--->v[i] (權值爲w[i])這條邊,使的1號頂點的距離變短。即1號頂點到u[i]號頂點的距離(dis[u[i]]) 加上 u[i] ---> v[i]這條邊(權值爲w[i])的值是否比原來1號頂點到v[i]號距離(dis[v[i]])要小。這點感覺和迪傑斯特拉的鬆弛操作有點兒像。。。
我們把每一條邊都鬆弛一遍後會怎麼樣呢?我們來舉個例子,求下圖1號頂點到其餘所有頂點的最短路徑。
我們還是用一個dis數組來存儲1號頂點到所有頂點的距離。
我們先來初始化:
上方右圖中每個頂點旁的值爲該定點的最短路的“估計值”(當前1號頂點到他的距離),即dis中對應的值。根據邊給出的順序,先來處理一下第一條邊“2-3-2”(2--2-->3通過這條邊進行鬆弛)即判斷dis[3]是否大於dis[2]+2。此時的dis[3]是∞,dis[2]的值也是∞,因此dis[2] + 2也是∞,所以這條邊鬆弛失敗。
同時我們繼續處理第二條邊“1--2 -- -3” (1--“-3”-->2 ) 我們發現dis[2] > dis[1] + (-3) ,通過這條邊可以使dis[2]的值從∞變爲 -3 ,所以這個點鬆弛成功。我們可以用同樣的方法來處理剩下的一條邊,對所有的邊進行一遍鬆弛操作後的結果如下。
我們能發現,在對每一條邊都進行一次鬆弛操作後,已經使dis[2]和dis[5]的值變小,即1號頂點到2號頂點和1號頂點到5號頂點的距離都變短了。
接下來我們要對所有邊再進行一輪鬆弛操作,操作過程大致和上邊的一樣,再看看會有什麼變化。
只要進行n - 1 輪就可以了,因爲在一個含有n個頂點的圖中,任意兩點之間的最短路徑最多包含n-1條邊。
扯了半天,回到之前的例子,繼續進行第3輪和第4輪鬆弛操作,這裏只需進行4輪就可以了,因爲這個圖一共只有5個頂點。
這裏看似貌似不需要第四輪,因爲執行完第四輪沒有任何變化!沒錯,其實就是最多進行 n - 1 輪鬆弛。
整個算法用一句話概括就是:對所有的邊進行n-1次鬆弛操作。核心代碼就只有幾行,如下:
for(int k = 1 ; k <= n - 1 ; k ++) //進行n-1輪鬆弛
{
for(int i = 1 ; i < m ; i ++) // 枚舉每一條邊
{
if(dis[v[i]] > dis[u[i]] + w[i]) //嘗試對每一條邊鬆弛
dis[v[i]] = dis[u[i]] + w[i] ;
}
}
Bellman-Ford算法的完整的代碼如下。
#include<bits/stdc++.h>
const int INF = 99999999;
using namespace std;
int main()
{
int u[100] , v[100] , w[100] , dis[100] , n , m ;
cin>>n>>m;
for(int i = 1 ; i <= m ; i ++)
{
cin>>u[i] >> v[i] >> w[i];
}
for(int i = 1 ; i <= n ; i ++)
dis[i] = INF;
dis[1] = 0;
for(int k = 1 ; k <= n - 1 ; k ++)
for(int i = 1 ; i <= m ; i ++)
if(dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i];
for(int i = 1 ; i <= n ; i ++)
cout<<dis[i]<<" ";
return 0 ;
}
/*
5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
*/
除此之外,bellman-ford 算法還可以檢測出一個圖是否含有負權迴路。如果進行n-1輪鬆弛操作之後仍然存在if(dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i];
的情況,也就是說在進行n-1輪鬆弛後,仍可以繼續成功鬆弛,那麼此圖必然存在負權迴路。如果一個圖沒有負權迴路,那麼最短路徑所包含的邊最多爲n-1條,即進行n-1輪鬆弛操作後最短路不會再發生變化。如果在n-1輪鬆弛後最短路仍然可以 發生變化,則這個圖一定有負權迴路,管不見代碼如下:
for(int k = 1 ; k <= n - 1 ; k ++)
for(int i = 1 ; i <= m ; i ++)
if(dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i];
//檢測負權迴路
flag = 0 ;
for(int i = 1 ; i <= m ; i ++)
if(dis[v[i]] > dis[u[i]] + w[i])
flag = 1 ;
if(flag == 1)
printf("此圖沒有負權迴路\n");
#include<bits/stdc++.h>
const int INF = 9999999;
using namespace std;
int main()
{
int u[100] , v[100] , w[100] , dis[100] , n , m , ck , flag;
cin>>n>>m;
for(int i = 1 ; i <= m ; i ++)
{
cin>>u[i] >> v[i] >> w[i];
}
for(int i = 1 ; i <= n ; i ++)
dis[i] = INF;
dis[1] = 0;
for(int k = 1 ; k <= n - 1 ; k ++)
{
ck = 0 ; //用來標記本輪鬆弛操作中數組dis是否會發生更新
for(int i = 1 ; i <= m ; i ++)
{
if(dis[v[i]] > dis[u[i]] + w[i])
{
dis[v[i]] = dis[u[i]] + w[i];
ck = 1 ; //數組dis發生更新,改變check的值
}
}
if(ck == 0)
break; //如果dis數組沒有更新,提前退出循環結束算法
}
flag = 0 ;
for(int i = 1 ; i <= m ; i ++)
if(dis[v[i]] > dis[u[i]] + w[i])
flag = 1;
if(flag == 1)
printf("此圖包含有負權迴路\n");
else
{
for(int i = 1 ; i <= n ; i ++)
printf("%d ",dis[i]);
}
return 0 ;
}
/*
5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
*/
美國應用數學家Richard Bellman (理查德。貝爾曼)於1958 年發表了該算法。此外Lester Ford, Jr在1956年也發表了該算法。因此這個算法叫做Bellman-Ford算法。其實EdwardF. Moore在1957年也發表了同樣的算法,所以這個算法也稱爲Bellman-Ford-Moore算法。Edward F. Moore很熟悉對不對?就是那個在“如何從迷宮中尋找出路”問題中提出了廣度優先搜索算法的那個傢伙。
例題: