Bellman-Ford
算法運行結束後,會獲得從源節點 s
到其它全部節點的最短路徑,同時獲得每一個節點的前驅節點,Bellman-Ford
不能包含負權迴路如圖 1.1
但能夠包含圖 1.2
,這裏所說的負權環路是指環路的權值總和爲正或爲負算法
圖 1.1
瀏覽器
圖 1.2
數據結構
鬆弛操做針對的操做對象是圖中的邊,對圖中任意一條邊e=(u,v)
,假設在對e
進行鬆弛以前,已經知道從源節點s
到u
的最短估計距離u.d
,從源點到v的最短估距離v.d
,同時邊e
的權重爲w
,鬆弛操做就是更新節點v的最短估計距離v.d = min{v.d, u.d + w}
, 因爲初始狀態是,全部節點的最短估計路徑都設爲 Infinity
即無窮大,因此在任意時刻,u.d
和v.d
都是存在的this
初始時,v1,v2,v3,v4四個節點的最短估計路徑都爲 Infinity ,求解從v1節點到其它全部節點的最短路徑距離,因此將v1.d設置爲0spa
圖 2.2
prototype
(v1,v2)
進行鬆弛 有 v1.d = 0,v2.d = Infinity,w(v1,v2) = 1;
因此v2.d
被更新爲 v2.d = v1.d + w(v1,v2) = 1;
(v1,v3)
進行鬆弛 有 v1.d = 0,v3.d = Infinity,w(v1,v3) = 3;
因此v3.d
被更新爲 v3.d = v1.d + w(v1,v3) = 3;
(v2,v4)
進行鬆弛 有 v2.d = 1,v4.d = Infinity,w(v2,v4) = 5;
因此v4.d
被更新爲 v4.d = v2.d + w(v2,v4) = 6;
(v3,v4)
進行鬆弛 有 v3.d = 3,v4.d = 6,w(v3,v4) = 1;
因此v4.d
被更新爲 v4.d = v3.d + w(v3,v4) = 4;
在全局使用 Infinity
來表示正無窮大,用 -Infinity
表示負無窮大,同時可使用 Number.POSITIVE_INFINITY
表示正無窮,用Number.NEGATIVE_INFINITY
表示負無窮,這幾個常量均可以與其它類型的數字比較大小,在 Number
中還有其它的常量,讀者能夠在新版的瀏覽器控制檯 執行 console.dir(Number)
去查看3d
//節點數據結構 function Vertex() { if (!(this instanceof Vertex)) return new Vertex(); this.id = null; //用來標識節點 this.data = null; //節點數據 } //邊數據結構 function Edge() { if (!(this instanceof Edge)) return new Edge(); this.u = null; //邊的起點節點 this.v = null; //邊的終點節點 this.w = null; //邊的權重 } //圖 function Graph() { if (!(this instanceof Graph)) return new Graph(); this.vertices = []; //圖的節點集 做爲算法的輸入數據 this.edges = []; //圖的邊集 做爲算法的輸入數據 this.refer = new Map(); //節點標識表 } Graph.prototype = { constructor: Graph, initVertices: function(vs) { for (let id of vs) { let v = Vertex(); v.id = id; this.refer.set(id, v); this.vertices.push(v); } }, initEdges: function(es) { for (let r of es) { let e = Edge(); e.u = this.refer.get(r.u); e.v = this.refer.get(r.v); e.w = r.w; this.edges.push(e); } } } var vertices = ['v1', 'v2', 'v3', 'v4']; var edges = [ {u:'v1', v:'v2', w:1}, {u:'v1', v:'v3', w:3}, {u:'v2', v:'v4', w:5}, {u:'v3', v:'v4', w:1}, {u:'v4', v:'v2', w:-3} ]; var g = Graph(); g.initVertices(vertices); g.initEdges(edges);
BellmanFord算法的原理就是對輸入的全部邊都進行 |V| - 1
次鬆弛操做,爲何是 |V| - 1
次見 5.3.code
function BellmanFord(vertices, edges, source) { let distance = new Map(); //用來記錄從原節點 source 到某個節點的最短路徑估計值 let predecessor = new Map(); //用來記錄某個節點的前驅節點 // 第一步: 初始化圖 for (let v of vertices) { distance.set(v, Infinity); // 初始化最短估計距離 默認無窮大 predecessor.set(v, null); // 初始化前驅節點 默認爲空 } distance.set(source, 0); // 將源節點的最短路徑估計距離 初始化爲0 // 第二步: 重複鬆弛邊 for (let i = 1, len = vertices.length - 1; i < len; i++) { for (let e of edges) { if (distance.get(e.u) + e.w < distance.get(e.v)) { distance.set(e.v, distance.get(e.u) + e.w); predecessor.set(e.v, e.u); } } } // 第三步: 檢查是否有負權迴路 第三步必須在第二步後面 for (let e of edges) { if (distance.get(e.u) + e.w < distance.get(e.v)) return null; //返回null表示包涵負權迴路 } return { distance: distance, predecessor: predecessor } }
|V| - 1
次最外層增長循環且次數爲|V| - 1
次,緣由是對輸入的邊的順序是沒有限制的,在 2.2.節
中,咱們用了四次鬆弛操做就找到了從節點v1
到其它全部節點的最短路徑,是由於 2.2.節
中邊是按照必定的順序選取的,開始時選取的是與源節點直接相領的邊,接下來選取邊的起始節點是已經被鬆弛過的邊鏈接的終止節點,若是對邊的選取順序爲 (v2,v4),(v3,v4),(v1,v2),(v1,v3)
這種狀況就須要最外層的循環,而且須要兩次,考慮最壞的狀況,如圖對象
圖 5.3
blog
而且邊的選取順序爲(v3,v4),(v2,v3),(v1,v2)
,這樣對於四個節點須要三次最外層的循環,即|V| - 1
在《算法導論》中,有這樣的描述:
當進行第 i
次循環時,必定包含邊 (v[i-1],v[i])
, 這句話的意思時,若是存在從源節點s
到v
的最短路徑,那麼在第i次循環結束後,節點 v[i-1].d
和節點v[i].d
必定不爲 Infinity
,爲一個具體的值
輸入圖爲 圖 1.2
從 節點v1到其它全部節點的最短路徑
//節點數據結構 function Vertex() { if (!(this instanceof Vertex)) return new Vertex(); this.id = null; //用來標識節點 this.data = null; //節點數據 } //邊數據結構 function Edge() { if (!(this instanceof Edge)) return new Edge(); this.u = null; //邊的起點節點 this.v = null; //邊的終點節點 this.w = null; //邊的權重 } //圖 function Graph() { if (!(this instanceof Graph)) return new Graph(); this.vertices = []; //圖的節點集 this.edges = []; //圖的邊集 this.refer = new Map(); //節點標識表 } Graph.prototype = { constructor: Graph, initVertices: function(vs) { for (let id of vs) { let v = Vertex(); v.id = id; this.refer.set(id, v); this.vertices.push(v); } }, initEdges: function(es) { for (let r of es) { let e = Edge(); e.u = this.refer.get(r.u); e.v = this.refer.get(r.v); e.w = r.w; this.edges.push(e); } } } function BellmanFord(vertices, edges, source) { let distance = new Map(); //用來記錄從原節點 source 到某個節點的最短路徑估計值 let predecessor = new Map(); //用來記錄某個節點的前驅節點 // 第一步: 初始化圖 for (let v of vertices) { distance.set(v, Infinity); // 初始化最短估計距離 默認無窮大 predecessor.set(v, null); // 初始化前驅節點 默認爲空 } distance.set(source, 0); // 將源節點的最短路徑估計距離 初始化爲0 // 第二步: 重複鬆弛邊 for (let i = 1, len = vertices.length - 1; i < len; i++) { for (let e of edges) { if (distance.get(e.u) + e.w < distance.get(e.v)) { distance.set(e.v, distance.get(e.u) + e.w); predecessor.set(e.v, e.u); } } } // 第三步: 檢查是否有負權迴路 第三步必須在第二步後面 for (let e of edges) { if (distance.get(e.u) + e.w < distance.get(e.v)) return null; //返回null表示包涵負權迴路 } return { distance: distance, predecessor: predecessor } } var vertices = ['v1', 'v2', 'v3', 'v4']; var edges = [ {u:'v1', v:'v2', w:1}, {u:'v1', v:'v3', w:3}, {u:'v2', v:'v4', w:5}, {u:'v3', v:'v4', w:1}, {u:'v4', v:'v2', w:-3} ]; var g = Graph(); g.initVertices(vertices); g.initEdges(edges); var r = BellmanFord(g.vertices, g.edges, g.vertices[0]); console.log(r);