摘自百度百科
Bellman-ford算法是求含負權圖的單源最短路徑算法,效率很低,但代碼很容易寫。即進行不停地松弛(relaxation),每次松弛把每條邊都更新一下,若n-1次松弛后還能更新,則說明圖中有負環(即負權回路,本文最后有解釋),無法得出結果,否則就成功完成。Bellman-ford算法有一個小優化:每次松弛先設一個旗幟flag,初值為FALSE,若有邊更新則賦值為TRUE,最終如果還是FALSE則直接成功退出。Bellman-ford算法浪費了許多時間做無必要的松弛,所以SPFA算法用隊列進行了優化,效果十分顯著,高效難以想象。SPFA還有SLF,LLL,滾動數組等優化。
Dijkstra算法中不允許邊的權是負權,如果遇到負權,則可以采用Bellman-Ford算法。
Bellman-Ford算法能在更普遍的情況下(存在負權邊)解決單源點最短路徑問題。對於給定的帶權(有向或無向)圖 G=(V,E),其源點為s,加權函數w是 邊集 E 的映射。對圖G運行Bellman-Ford算法的結果是一個布爾值,表明圖中是否存在着一個從源點s可達的負權回路。若不存在這樣的回路,算法將給出從源點s到 圖G的任意頂點v的最短路徑d[v]。
適用條件&范圍
1.單源最短路徑(從源點s到其它所有頂點v);
2.有向圖&無向圖(無向圖可以看作(u,v),(v,u)同屬於邊集E的有向圖);
3.邊權可正可負(如有負權回路輸出錯誤提示);
4.差分約束系統;
Bellman-Ford算法描述:
1,.初始化:將除源點外的所有頂點的最短距離估計值 d[v] ←+∞, d[s] ←0;
2.迭代求解:反復對邊集E中的每條邊進行松弛操作,使得頂點集V中的每個頂點v的最短距離估計值逐步逼近其最短距離;(運行|v|-1次)
3.檢驗負權回路:判斷邊集E中的每一條邊的兩個端點是否收斂。如果存在未收斂的頂點,則算法返回false,表明問題無解;否則算法返回true,並且從源點可達的頂點v的最短距離保存在 d[v]中。
描述性證明:
首先指出,圖的任意一條最短路徑既不能包含負權回路,也不會包含正權回路,因此它最多包含|v|-1條邊。
其次,從源點s可達的所有頂點如果 存在最短路徑,則這些最短路徑構成一個以s為根的最短路徑樹。Bellman-Ford算法的迭代松弛操作,實際上就是按頂點距離s的層次,逐層生成這棵最短路徑樹的過程。
在對每條邊進行1遍松弛的時候,生成了從s出發,層次至多為1的那些樹枝。也就是說,找到了與s至多有1條邊相聯的那些頂點的最短路徑;對每條邊進行第2遍松弛的時候,生成了第2層次的樹枝,就是說找到了經過2條邊相連的那些頂點的最短路徑……。因為最短路徑最多只包含|v|-1 條邊,所以,只需要循環|v|-1 次。
每實施一次松弛操作,最短路徑樹上就會有一層頂點達到其最短距離,此后這層頂點的最短距離值就會一直保持不變,不再受后續松弛操作的影響。(但是,每次還要判斷松弛,這里浪費了大量的時間,怎么優化?單純的優化是否可行?)
如果沒有負權回路,由於最短路徑樹的高度最多只能是|v|-1,所以最多經過|v|-1遍松弛操作后,所有從s可達的頂點必將求出最短距離。如果 d[v]仍保持 +∞,則表明從s到v不可達。
如果有負權回路,那么第 |v|-1 遍松弛操作仍然會成功,這時,負權回路上的頂點不會收斂。
C++ pseudo code
Bellman-Ford(G,w,s) :boolean //圖G ,邊集 函數 w ,s為源點 for each vertex v ∈ V(G) do //初始化 1階段 d[ v] ←+∞ d[s] ←0; //1階段結束 for i=1 to |v|-1 do //2階段開始,雙重循環。 for each edge(u,v) ∈E(G) do //邊集數組要用到,窮舉每條邊。 If d[v]> d[u]+ w(u,v) then //松弛判斷,w(w,v)是u到v的權值 d[v]=d[u]+w(u,v) //松弛操作 2階段結束 for each edge(u,v) ∈E(G) do If d[v]> d[u]+ w(u,v) then Exit false //存在負權回路 Exit true
負權回路
在一個圖里每條邊都有一個權值(有正有負)
如果存在一個環(從某個點出發又回到自己的路徑),而且這個環上所有權值之和是負數,那這就是一個負權環,也叫負權回路
存在負權回路的圖是不能求兩點間最短路的,因為只要在負權回路上不斷兜圈子,所得的最短路長度可以任意小。(轉自百度知道)
代碼實現:
Bellman-ford算法的運行時間為O(VE),V為頂點數,E為邊數。
/************************************************************************* > File Name: Bellman_ford.cpp > Author: He Xingjie > Mail: gxmshxj@163.com > Created Time: 2014年06月08日 星期日 22時33分07秒 > Description: ************************************************************************/ #include<iostream> #include<cstdio> using namespace std; #define INF 99999 int map[100][100], dist[100]; bool Bellman_ford(int n, int s) { int v, u; for (v=1; v<n; v++) { if (map[s][v] == INF) dist[v] = INF; else dist[v] = map[s][v]; } dist[s] = 0; for (v=1; v<n; v++) for (u=0; u<n; u++) if (map[u][v] < INF) //u->v has path if (dist[v] > dist[u] + map[u][v]) dist[v] = dist[u] + map[u][v]; //遍歷所有的邊 for (v=0; v<n; v++) for (u=0; u<n; u++) if (v != u && map[u][v] != INF) if (dist[v] > dist[u] + map[v][u]) return false; return true; } void PrintMap(int n) { int i, j; //輸出矩陣 for (i=0; i<n; i++) { for (j=0; j<n; j++) { if (map[i][j] == INF) printf("INF "); else printf("%d ", map[i][j]); } printf("\n"); } } void PrintShortestValue(int n) { int i; for (i=1; i<n; i++) printf("%d:%d ", i, dist[i]); printf("\n"); } int main() { int n, m, i, j; freopen("input.txt", "r", stdin); cin>>n>>m; //n是頂點數,m是邊數 //初始化 for (i=0; i<n; i++) { for (j=0; j<n; j++) map[i][j] = INF; } //輸入 for(int i=1; i<=m; i++) { int i,j; cin>>i>>j; cin>>map[i][j]; } PrintMap(n); Bellman_ford(n, 0); PrintShortestValue(n); return 0; }