深入解析Bellman Ford's Algorithm,高效解決含有負權(quán)重圖中的最短路徑問題

概述

Bellman Ford算法可以用來解決加權(quán)圖中的最短路徑問題。其與Dijkstra算法的區(qū)別在于Belllman Ford算法的應用范圍更廣,例如其可以用來處理帶有負權(quán)重的加權(quán)圖中的最短路徑問題。由于Dijkstra算法本質(zhì)上是一種貪心算法,因而當圖中存在路徑權(quán)值之和為負的環(huán)時,Dijkstra算法會給出錯誤的結(jié)果因為其總是偏向于選擇當前情況下的局部最優(yōu)路徑。Bellman Ford算法的時間復雜度高于Dijkstra算法。

歷史

Bellman Ford算法最初于1955年被Alfonso Shimbel提出,但最終基于1958和1956年發(fā)表論文的 Richard Bellman和Lester Ford, Jr.二人命名。Edward F. Moore于1957年也提出了該算法,因此該算法有時也稱作是Bellman Ford Moore算法。

負權(quán)重帶環(huán)圖的問題

包含負權(quán)重的圖在不少實際問題中有所應用。例如在尋找化學反應鏈中需要最少能量的路徑中,該反應鏈可能既包含吸熱反應也包含放熱反應,那么我們可以認為放熱反應是負權(quán)重路徑而吸熱反應是正權(quán)重路徑。在包含負權(quán)重帶環(huán)圖的問題中,從起始點開始的一條路徑如果能夠到達一個整體權(quán)重之和為負的環(huán),則最短路徑在中情況下將不存在,因為這一路徑總可以通過在負權(quán)重路徑中循環(huán)來不斷降低總權(quán)重。而使用Bellman Ford算法則能夠發(fā)現(xiàn)這一問題。

算法描述

簡單來說,類似于Dijkstra算法,Bellman Ford算法首先將從源結(jié)點到達剩余所有結(jié)點的距離初始化為無窮大,而從源結(jié)點到其本身的距離則初始化為0。然后算法將循環(huán)檢查圖中的每一條邊。如果這條邊能夠縮短從源結(jié)點到某一目的結(jié)點的距離,則新的最短距離將被記錄下來。具體來說,對于一條邊u -> v,設(shè)其權(quán)重為weight(u, v),而結(jié)點u,v距離源結(jié)點src的距離分別為dist[u],dist[v]。則dist[v] = min(dist[v], dist[u] + weight(u,v)。在每一次循環(huán)中該算法都檢查所有的邊。對于第i次循環(huán),算法將得到從源結(jié)點出發(fā)不超過i步能夠到達的結(jié)點的最短距離(在某些情況下也可能多于i)。因為對于N個結(jié)點的圖來說,不包含環(huán)的最短距離最長為N-1,所以該算法需要循環(huán)檢查所有邊N-1次。

在循環(huán)結(jié)束后,算法將再多循環(huán)檢查一遍所有的邊并嘗試更新最短路徑。如果從源結(jié)點出發(fā)到某一點的最短距離在這一次循環(huán)中能夠被更新,則說明在這一路徑上至少存在一個權(quán)重之和為負的環(huán)。

算法的Java實現(xiàn)

public class BellmanFord {
    /**
     * Find the shortest path from src to all other nodes in the graph 
     * represented using an array of arrays of shape [u, v, weight],
     * in which u and v are the start and end point of the edge, respectively.
     * @param  n     Number of nodes in the graph.
     * @return       An array containing the shortest path from src to all other 
     * nodes in the graph, with the predecessor of the nodes. For each node i, the
     * array contains a two-tuple [distance, predecessor]. 
     * If a shortest path from src to node does not exist, distance and predecessor will
     * both be Integer.MAX_VALUE.
     */
    public int[][] findShortestPath(int src, int n, int[][] edges) {
        // first step, initialize the distance
        int[] dist = new int[n];
        int[] pre = new int[n];
        int INF = Integer.MAX_VALUE / 2;
        Arrays.fill(dist, INF);
        dist[src] = 0;
        Arrays.fill(pre, Integer.MAX_VALUE);

        // second step, compute shortest path with at most n-1 steps
        for (int i = 1; i < n; i++) {
            for (int[] edge : edges) {
                int u = edge[0], v = edge[1], weight = edge[2];
                if (dist[v] > dist[u] + weight) {
                    pre[v] = u;
                }
                dist[v] = Math.min(dist[v], dist[u] + weight);
            }
        }

        // third step, check the existance of negative weight cycle
        for (int[] edge : edges) {
            int u = edge[0], v = edge[1], weight = edge[2];
            if (dist[u] < INF && dist[u] + weight < dist[v]) {
                System.out.println("Graph contains negative weight cycle");
            }
        }

        int[][] ret = new int[n][2];
        for (int i = 0; i < n; i++) {
            ret[i] = new int[]{dist[i] == INF ? Integer.MAX_VALUE : dist[i], pre[i]};
        }

        return ret;
    }
}

時間復雜度

從上面代碼中我們可以看出我們在每一次循環(huán)中都會檢查所有的邊,共計循環(huán)了N次,那么算法的時間復雜度為O(N * E),其中N為圖中的結(jié)點總數(shù),E為圖中的邊的數(shù)目。

空間復雜度

O(N)。因為我們使用了O(N)大小的數(shù)組來存儲最短路徑的距離。

潛在優(yōu)化

對于step 2,如果在某一次循環(huán)中我們沒有更新任何最短距離,則這一循環(huán)可以提前結(jié)束。因為這證明該算法已經(jīng)找到了從源結(jié)點出發(fā)能夠到達的所有結(jié)點的最短路徑。這一優(yōu)化使得step 2的循環(huán)次數(shù)能夠少于O(N-1)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容