-
Notifications
You must be signed in to change notification settings - Fork 234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Bellman-Ford algorithm #88
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
var _ = require("../lodash"); | ||
|
||
module.exports = bellmanFord; | ||
|
||
var DEFAULT_WEIGHT_FUNC = _.constant(1); | ||
|
||
function bellmanFord(g, source, weightFn, edgeFn) { | ||
return runBellmanFord( | ||
g, | ||
String(source), | ||
weightFn || DEFAULT_WEIGHT_FUNC, | ||
edgeFn || function(v) { return g.outEdges(v); } | ||
); | ||
} | ||
|
||
|
||
function runBellmanFord(g, source, weightFn, edgeFn) { | ||
var results = {}, | ||
didADistanceUpgrade = true, | ||
iterations = 0, | ||
nodes = g.nodes(); | ||
|
||
var relaxEdge = function(edge) { | ||
var edgeWeight = weightFn(edge); | ||
if( results[edge.v].distance + edgeWeight < results[edge.w].distance ){ | ||
mstou marked this conversation as resolved.
Show resolved
Hide resolved
mstou marked this conversation as resolved.
Show resolved
Hide resolved
|
||
results[edge.w] = { | ||
distance: results[edge.v].distance + edgeWeight, | ||
predecessor: edge.v | ||
}; | ||
didADistanceUpgrade = true; | ||
} | ||
}; | ||
|
||
var relaxAllEdges = function() { | ||
nodes.forEach(function(vertex) { | ||
edgeFn(vertex).forEach(function(edge) { | ||
// If the vertex on which the edgeFun in called is | ||
// the edge.w, then we treat the edge as if it was reversed | ||
var inVertex = edge.v === vertex ? edge.v : edge.w; | ||
var outVertex = inVertex === edge.v ? edge.w : edge.v; | ||
relaxEdge({ v: inVertex, w: outVertex }); | ||
}); | ||
}); | ||
}; | ||
|
||
// Initialization | ||
nodes.forEach(function(v) { | ||
var distance = v === source ? 0 : Number.POSITIVE_INFINITY; | ||
results[v] = { distance: distance }; | ||
}); | ||
|
||
var numberOfNodes = nodes.length; | ||
|
||
// Relax all edges in |V|-1 iterations | ||
for(var i = 1; i < numberOfNodes; i++){ | ||
didADistanceUpgrade = false; | ||
iterations++; | ||
relaxAllEdges(); | ||
if (!didADistanceUpgrade) { | ||
// Ιf no update was made in an iteration, Bellman-Ford has finished | ||
break; | ||
} | ||
} | ||
|
||
// Detect if the graph contains a negative weight cycle | ||
if (iterations === numberOfNodes - 1) { | ||
didADistanceUpgrade = false; | ||
relaxAllEdges(); | ||
if (didADistanceUpgrade) { | ||
throw new Error("The graph contains a negative weight cycle"); | ||
} | ||
} | ||
|
||
return results; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
module.exports = { | ||
bellmanFord: require("./bellman-ford"), | ||
components: require("./components"), | ||
dijkstra: require("./dijkstra"), | ||
dijkstraAll: require("./dijkstra-all"), | ||
|
@@ -8,6 +9,7 @@ module.exports = { | |
postorder: require("./postorder"), | ||
preorder: require("./preorder"), | ||
prim: require("./prim"), | ||
shortestPaths: require("./shortest-paths"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks good that you extract it but the documentation will need to mention that this API can run bellman ford or Dijkstra |
||
tarjan: require("./tarjan"), | ||
topsort: require("./topsort") | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
var dijkstra = require("./dijkstra"), | ||
bellmanFord = require("./bellman-ford"); | ||
|
||
module.exports = shortestPaths; | ||
|
||
function shortestPaths(g, source, weightFn, edgeFn){ | ||
return runShortestPaths( | ||
g, | ||
source, | ||
weightFn, | ||
edgeFn || function(v) { return g.outEdges(v); } | ||
); | ||
} | ||
|
||
function runShortestPaths(g, source, weightFn, edgeFn) { | ||
if (weightFn === undefined) { | ||
return dijkstra(g, source, weightFn, edgeFn); | ||
} | ||
|
||
var negativeEdgeExists = false; | ||
var nodes = g.nodes(); | ||
|
||
for (var i = 0; i < nodes.length; i++) { | ||
var adjList = edgeFn(nodes[i]); | ||
|
||
for (var j = 0; j < adjList.length; j++) { | ||
var edge = adjList[j]; | ||
var inVertex = edge.v === nodes[i] ? edge.v : edge.w; | ||
var outVertex = inVertex === edge.v ? edge.w : edge.v; | ||
|
||
if (weightFn({ v: inVertex, w: outVertex }) < 0) { | ||
negativeEdgeExists = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can break in case a negative edge was found |
||
} | ||
} | ||
|
||
if (negativeEdgeExists) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can extract this if outside the scope and just write: |
||
return bellmanFord(g, source, weightFn, edgeFn); | ||
} | ||
} | ||
|
||
return dijkstra(g, source, weightFn, edgeFn); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
var expect = require("../chai").expect, | ||
Graph = require("../..").Graph, | ||
bellmanFord = require("../..").alg.bellmanFord, | ||
shortestPathsTests = require("./shortest-paths-tests.js"); | ||
|
||
describe("alg.bellmanFord", function(){ | ||
shortestPathsTests(bellmanFord); | ||
|
||
it("Works with negative weight edges on the graph", function() { | ||
var g = new Graph(); | ||
g.setEdge("a", "b", -1); | ||
g.setEdge("a", "c", 4); | ||
g.setEdge("b", "c", 3); | ||
g.setEdge("b", "d", 2); | ||
g.setEdge("b", "e", 2); | ||
g.setEdge("d", "c", 5); | ||
g.setEdge("d", "b", 1); | ||
g.setEdge("e", "d", -3); | ||
|
||
expect(bellmanFord(g, "a", weightFn(g))).to.eql({ | ||
a: { distance: 0 }, | ||
b: { distance: -1, predecessor: "a" }, | ||
c: { distance: 2, predecessor: "b" }, | ||
d: { distance: -2, predecessor: "e" }, | ||
e: { distance: 1, predecessor: "b" } | ||
}); | ||
}); | ||
|
||
it("Throws an error if the graph contains a negative weight cycle", function() { | ||
var g = new Graph(); | ||
g.setEdge("a", "b", 1); | ||
g.setEdge("b", "c", 3); | ||
g.setEdge("c", "d", -5); | ||
g.setEdge("d", "e", 4); | ||
g.setEdge("d", "b", 1); | ||
g.setEdge("c", "f", 8); | ||
|
||
expect(function() { bellmanFord(g, "a", weightFn(g)); } ).to.throw(); | ||
}); | ||
}); | ||
|
||
|
||
function weightFn(g) { | ||
return function(e) { | ||
return g.edge(e); | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
var expect = require("../chai").expect, | ||
Graph = require("../..").Graph, | ||
alg = require("../..").alg; | ||
|
||
module.exports = tests; | ||
|
||
function tests(algorithm) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe adding a comment that this reflect for bellman ford or Dijkstra? |
||
describe( "Shortest Path Algorithms", function() { | ||
it("assigns distance 0 for the source node", function() { | ||
var g = new Graph(); | ||
g.setNode("source"); | ||
expect(algorithm(g, "source")).to.eql({ source: { distance: 0 } }); | ||
}); | ||
|
||
it("returns Number.POSITIVE_INFINITY for unconnected nodes", function() { | ||
var g = new Graph(); | ||
g.setNode("a"); | ||
g.setNode("b"); | ||
expect(algorithm(g, "a")).to.eql({ | ||
a: { distance: 0 }, | ||
b: { distance: Number.POSITIVE_INFINITY } | ||
}); | ||
}); | ||
|
||
it("returns the distance and path from the source node to other nodes", function() { | ||
var g = new Graph(); | ||
g.setPath(["a", "b", "c"]); | ||
g.setEdge("b", "d"); | ||
expect(algorithm(g, "a")).to.eql({ | ||
a: { distance: 0 }, | ||
b: { distance: 1, predecessor: "a" }, | ||
c: { distance: 2, predecessor: "b" }, | ||
d: { distance: 2, predecessor: "b" } | ||
}); | ||
}); | ||
|
||
it("works for undirected graphs", function() { | ||
var g = new Graph({ directed: false }); | ||
g.setPath(["a", "b", "c"]); | ||
g.setEdge("b", "d"); | ||
expect(algorithm(g, "a")).to.eql({ | ||
a: { distance: 0 }, | ||
b: { distance: 1, predecessor: "a" }, | ||
c: { distance: 2, predecessor: "b" }, | ||
d: { distance: 2, predecessor: "b" } | ||
}); | ||
}); | ||
|
||
it("uses an optionally supplied weight function", function() { | ||
var g = new Graph(); | ||
g.setEdge("a", "b", 1); | ||
g.setEdge("a", "c", 2); | ||
g.setEdge("b", "d", 3); | ||
g.setEdge("c", "d", 3); | ||
|
||
expect(algorithm(g, "a", weightFn(g))).to.eql({ | ||
a: { distance: 0 }, | ||
b: { distance: 1, predecessor: "a" }, | ||
c: { distance: 2, predecessor: "a" }, | ||
d: { distance: 4, predecessor: "b" } | ||
}); | ||
}); | ||
|
||
it("uses an optionally supplied edge function", function() { | ||
var g = new Graph(); | ||
g.setPath(["a", "c", "d"]); | ||
g.setEdge("b", "c"); | ||
|
||
expect(algorithm(g, "d", undefined, function(e) { return g.inEdges(e); })).to.eql({ | ||
a: { distance: 2, predecessor: "c" }, | ||
b: { distance: 2, predecessor: "c" }, | ||
c: { distance: 1, predecessor: "d" }, | ||
d: { distance: 0 } | ||
}); | ||
}); | ||
}); | ||
} | ||
|
||
// Test shortestPaths() function | ||
describe("alg.shortestPaths", function() { | ||
tests(alg.shortestPaths); | ||
|
||
it("uses dijkstra if no weightFn is provided", function() { | ||
var g = new Graph(); | ||
g.setPath(["a", "b", "c"]); | ||
g.setEdge("b", "d", -10); | ||
|
||
expect(alg.shortestPaths(g, "a")).to.eql(alg.dijkstra(g, "a")); | ||
}); | ||
|
||
it("uses bellman-ford if the graph contains a negative edge", function() { | ||
var g = new Graph(); | ||
g.setEdge("a", "b", 10); | ||
g.setEdge("b", "c", 8); | ||
g.setEdge("a", "d", -3); | ||
g.setEdge("d", "c", 2); | ||
|
||
expect(alg.shortestPaths(g, "a", weightFn(g))).to.eql(alg.bellmanFord(g, "a", weightFn(g))); | ||
}); | ||
}); | ||
|
||
function weightFn(g) { | ||
return function(e) { | ||
return g.edge(e); | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we switch some of the 'var' declarations in this PR to let?