最短路徑是圖論中乙個很經典的問題:給定圖g(v,e),求一條從起點到終點的路徑,使得這條路徑上經過的所有邊的邊權之和最小。
對任意給出的圖g(v,e)和起點s、終點t,如何求從s到t的最短路徑。解決最短路徑問題的常用演算法有dijkstra演算法、bellman-ford演算法、spea演算法和floyd演算法。
dijkstra演算法可以很好地解決無負權圖的最短路徑問題,但如果出現了負權邊,dijkstra演算法就會失效,例如下圖中設定a為源點時,首先會將點b和點c的dist值變為-1和1,接著由於點b的dist值最小,因此用點b去更新其未訪問的鄰接點(雖然並沒有)。在這之後點b標記為己訪問,於是將無法被從點c出發的邊cb更新,因此最後 dist[b]就是-1,但顯然a到b的最短路徑長度應當是a→c→b的4。
為了更好地求解有負權邊的最短路徑問題,需要使用bellman-ford演算法(簡稱bf演算法)。和dijkstra演算法一樣,bellman-ford演算法可解決單源最短路徑問題,但也能處理有負權邊的情況。
bellman-ford演算法的思路簡潔直接,易於讀者掌握。現在考慮環,也就是從某個頂點出發、經過若干個不同的頂點之後可以回到該頂點的情況。而根據環中邊的邊權之和的正負,可以將環分為零環、正環、負環(如下圖所示,環a→b→c中的邊權之和分別為零、正、負)。顯然,圖中的零環和正環不會影響最短路徑的求解,因為零環和正環的存在不能使最短路徑更短;而如果圖中有負環,且從源點可以到達,那麼就會影響最短路徑的求解;但如果圖中的負環無法從源點出發到達,則最短路徑的求解不會受到影響。
與dijkstra演算法相同,bellman-ford演算法設定乙個陣列d,用來存放從源點到達各個頂點的最短距離。同時bellman-ford演算法返回乙個bool值:如果其中存在從源點可達的負環,那麼函式將返回false;否則,函式將返回true,此時陣列d中存放的值就是從源點到達各頂點的最短距離。
bellman-ford演算法的主要思路如下面的偽**所示。需要對圖中的邊進行v-1輪操作,每輪都遍歷圖中的所有邊:對每條邊u→v,如果以u為中介點可以使d[v]更小,即d[u]+length[u->v]v]更新d[v]。同時也可以看出,bellman-ford演算法的時間複雜度是o(ve),其中n是頂點個數,e是邊數。
for(int i=0;iv)
}}
此時,如果圖中沒有從源點可達的負環,那麼陣列d中的所有值都應當已經達到最優。因此,如下面的偽**所示,只需要再對所有邊進行一輪操作,判斷是否有某條邊u→v仍然滿足d[u]+length[u->v]v)
}首先,如果最短路徑存在,那麼最短路徑上的頂點個數肯定不會超過v個。於是,如果把源點s作為一棵樹的根結點,把其他結點按照最短路徑的結點順序連線,就會生成一棵最短路徑樹。顯然,在最短路徑樹中,從源點s到達其餘各頂點的路徑就是原圖中對應的最短路徑,且原圖和源點一旦確定,最短路徑樹也就確定了。另外,由於最短路徑上的頂點個數不超過個,因此最短路徑樹的層數一定不超過v。
由於初始狀態下d[s]為0,因此在接下來的步驟中d[s]不會被改變(也就是說,最短路徑樹中第一層結點的d值被確定)。接著,,通過bellman-ford演算法的第一輪操作之後,最短路徑樹中的第二層頂點的d值也會被確定下來;然後進行第二輪操作,於是第三層頂點的d值也被確定下來。這樣計算直到最後一層頂點的d值確定。由於最短路徑樹的層數不超過v層,因此 bellman-ford演算法的鬆弛操作不會超過v-1輪。證畢。由於 bellman-ford演算法需要遍歷所有邊,顯然使用鄰接表會比較方便;如果使用鄰接矩陣,則時間複雜度會上公升到o(v^3)。因此下面的**將使用鄰接表作為舉例:
struct node;
vectoradj[maxv]; //圖 g,adj[u]存放從頂點u出發可以到達的所有頂點
int n; // n為頂點數,圖g使用鄰接表實現,maxv為最大頂點數
int d[maxv]; //起點到達各點的最短路徑長度
void dijkstra(int s)
} } }
}
這種優化後的演算法被稱為spfa(shortest path faster algorithm),它的期望時間複雜度是o(ke),其中e是圖的邊數,,k是乙個常數,在很多情況下k不超過2,可見這個演算法在大部分資料時異常高效,並且經常性地優於堆優化的dijkstra演算法。但如果圖中有從源點可達的負環,傳統spfa的時間複雜度就會退化成o(ve)。理解spfa的關鍵是理解它是如何從bellman-ford演算法優化得來的,因此,如果沒有理解,可以再閱讀一下上面的部分。下面給出鄰接表表示的圖的spea**,讀者可以把它和偽**結合起來看,以便理解。另外,如果事先知道圖中不會有環,那麼num陣列的部分可以去掉。注意:使用spfa可以判斷是否存在從源點可達的負環,如果負環從源點不可達,則需要新增乙個輔助頂點c,並新增一條從源點到達c的有向邊以及v-1條從c到達除源點外各頂點的有向邊才能判斷負環是否存在(了解)。
vectoradj[maxv]; //圖g的鄰接表
int n,d[maxv],num[maxv]; //num陣列記錄頂點的入隊次數
bool inq[maxv]; //頂點是否在佇列中
bool spea(int s)
} }
} return true; //無可達負環
}
spfa十分靈活,其內部的寫法可以根據具體場景的不同進行調整。例如上面**中的fifo佇列可以替換成優先佇列(prionity_queue),以加快速度;或者替換成雙端佇列( deque),使用slf優化和lll優化,以使效率提高至少50%。除此之外,上面給出的**是spfa的bfs版本,如果將佇列替換成棧,則可以實現dfs版本的spfa,對判環有奇效。 Bellman Ford演算法 和 SPFA演算法
bellman ford演算法是求含負權圖 的單源最短路徑演算法,效率很低,但 很容易寫。即進行不停地鬆弛 relaxation 每次鬆弛把每條邊都更新一下,若n 1次鬆弛後還能更新,則說明圖中有負環 即負權迴路,本文最後有解釋 無法得出結果,否則就成功完成。bellman ford演算法有乙個小優...
Bellman Ford演算法和SPFA演算法
演算法介紹 dijkstra可以解決單源無負邊最短路徑問題。但是當遇到含有負邊的單源最短路徑問題就需要使用bellman ford演算法來解決。bellman ford演算法還可以檢測出負環。演算法步驟 我們可以看到bellman ford演算法的演算法結構是比較簡單的,複雜度是o v e o ve...
Bellman Ford演算法和Dijkstra演算法
bellman ford演算法是通過relax邊來實現的,由於最短無負權迴路的路徑應該最多有v 1條邊,所以一共執行v 1次relax操作即可,而且注意,每次relax操作都只是基於上一次relax操作之後的圖,和這次relax中已經relax了的節點毫無關係 這個是重點!檢查負權迴路原理 若有負權...