From 5788575718aec183b676e0d1befbe8889f5093dd Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Thu, 3 May 2018 09:58:00 +0300 Subject: [PATCH] Add Bellman-Ford. --- README.md | 7 ++- src/algorithms/graph/bellman-ford/README.md | 21 +++++++ .../bellman-ford/__test__/bellmanFord.test.js | 56 +++++++++++++++++++ .../graph/bellman-ford/bellmanFord.js | 45 +++++++++++++++ .../graph/dijkstra/__test__/dijkstra.test.js | 2 + src/algorithms/graph/dijkstra/dijkstra.js | 5 +- src/data-structures/graph/Graph.js | 7 +++ .../graph/__test__/Graph.test.js | 4 ++ 8 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 src/algorithms/graph/bellman-ford/README.md create mode 100644 src/algorithms/graph/bellman-ford/__test__/bellmanFord.test.js create mode 100644 src/algorithms/graph/bellman-ford/bellmanFord.js diff --git a/README.md b/README.md index 3a03cfa1..3ecdefc5 100644 --- a/README.md +++ b/README.md @@ -65,8 +65,8 @@ * **Graph** * [Depth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/depth-first-search) (DFS) * [Breadth-First Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/breadth-first-search) (BFS) - * [Dijkstra Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/dijkstra) - finding shortest path - * Bellman Ford + * [Dijkstra Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/dijkstra) - finding shortest path to all graph vertices + * [Bellman-Ford Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bellman-ford) - finding shortest path to all graph vertices * Detect Cycle * Topological Sorting * Eulerian path, Eulerian circuit @@ -84,7 +84,7 @@ * **Greedy** * [Unbound Knapsack Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/knapsack-problem) - * [Dijkstra Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/dijkstra) - finding shortest path + * [Dijkstra Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/dijkstra) - finding shortest path to all graph vertices * **Divide and Conquer** * [Euclidean Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/math/euclidean-algorithm) - calculate the Greatest Common Divisor (GCD) * [Permutations](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/permutations) (with and without repetitions) @@ -103,6 +103,7 @@ * [0/1 Knapsack Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/knapsack-problem) * [Integer Partition](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/math/integer-partition) * [Maximum Subarray](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/maximum-subarray) + * [Bellman-Ford Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bellman-ford) - finding shortest path to all graph vertices * **Backtracking** * **Branch & Bound** diff --git a/src/algorithms/graph/bellman-ford/README.md b/src/algorithms/graph/bellman-ford/README.md new file mode 100644 index 00000000..b28318b2 --- /dev/null +++ b/src/algorithms/graph/bellman-ford/README.md @@ -0,0 +1,21 @@ +# Bellman–Ford Algorithm + +The Bellman–Ford algorithm is an algorithm that computes shortest +paths from a single source vertex to all of the other vertices +in a weighted digraph. It is slower than Dijkstra's algorithm +for the same problem, but more versatile, as it is capable of +handling graphs in which some of the edge weights are negative +numbers. + +![Bellman-Ford](https://upload.wikimedia.org/wikipedia/commons/2/2e/Shortest_path_Dijkstra_vs_BellmanFord.gif) + +## Complexity + +Worst-case performance `O(|V||E|)` +Best-case performance `O(|E|)` +Worst-case space complexity `O(|V|)` + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm) +- [On YouTube by Michael Sambol](https://www.youtube.com/watch?v=obWXjtg0L64) diff --git a/src/algorithms/graph/bellman-ford/__test__/bellmanFord.test.js b/src/algorithms/graph/bellman-ford/__test__/bellmanFord.test.js new file mode 100644 index 00000000..54519b09 --- /dev/null +++ b/src/algorithms/graph/bellman-ford/__test__/bellmanFord.test.js @@ -0,0 +1,56 @@ +import GraphVertex from '../../../../data-structures/graph/GraphVertex'; +import GraphEdge from '../../../../data-structures/graph/GraphEdge'; +import Graph from '../../../../data-structures/graph/Graph'; +import bellmanFord from '../bellmanFord'; + +describe('bellmanFord', () => { + it('should find minimum paths to all vertices', () => { + const vertexS = new GraphVertex('S'); + const vertexE = new GraphVertex('E'); + const vertexA = new GraphVertex('A'); + const vertexD = new GraphVertex('D'); + const vertexB = new GraphVertex('B'); + const vertexC = new GraphVertex('C'); + const vertexH = new GraphVertex('H'); + + const edgeSE = new GraphEdge(vertexS, vertexE, 8); + const edgeSA = new GraphEdge(vertexS, vertexA, 10); + const edgeED = new GraphEdge(vertexE, vertexD, 1); + const edgeDA = new GraphEdge(vertexD, vertexA, -4); + const edgeDC = new GraphEdge(vertexD, vertexC, -1); + const edgeAC = new GraphEdge(vertexA, vertexC, 2); + const edgeCB = new GraphEdge(vertexC, vertexB, -2); + const edgeBA = new GraphEdge(vertexB, vertexA, 1); + + const graph = new Graph(true); + graph + .addVertex(vertexH) + .addEdge(edgeSE) + .addEdge(edgeSA) + .addEdge(edgeED) + .addEdge(edgeDA) + .addEdge(edgeDC) + .addEdge(edgeAC) + .addEdge(edgeCB) + .addEdge(edgeBA); + + const { distances, previousVertices } = bellmanFord(graph, vertexS); + + expect(distances).toEqual({ + H: Infinity, + S: 0, + A: 5, + B: 5, + C: 7, + D: 9, + E: 8, + }); + + expect(previousVertices.H).toBeNull(); + expect(previousVertices.S).toBeNull(); + expect(previousVertices.B.getKey()).toBe('C'); + expect(previousVertices.C.getKey()).toBe('A'); + expect(previousVertices.A.getKey()).toBe('D'); + expect(previousVertices.D.getKey()).toBe('E'); + }); +}); diff --git a/src/algorithms/graph/bellman-ford/bellmanFord.js b/src/algorithms/graph/bellman-ford/bellmanFord.js new file mode 100644 index 00000000..70e811d9 --- /dev/null +++ b/src/algorithms/graph/bellman-ford/bellmanFord.js @@ -0,0 +1,45 @@ +/** + * @param {Graph} graph + * @param {GraphVertex} startVertex + * @return {{distances, previousVertices}} + */ +export default function bellmanFord(graph, startVertex) { + const distances = {}; + const previousVertices = {}; + + // Init all distances with infinity assuming that currently we can't reach + // any of the vertices except start one. + distances[startVertex.getKey()] = 0; + graph.getAllVertices().forEach((vertex) => { + previousVertices[vertex.getKey()] = null; + if (vertex.getKey() !== startVertex.getKey()) { + distances[vertex.getKey()] = Infinity; + } + }); + + // We need (|V| - 1) iterations. + for (let iteration = 0; iteration < (graph.getAllVertices().length - 1); iteration += 1) { + // During each iteration go through all vertices. + Object.keys(distances).forEach((vertexKey) => { + const vertex = graph.getVertexByKey(vertexKey); + + // Go through all vertex edges. + graph.getNeighbors(vertex).forEach((neighbor) => { + const edge = graph.findEdge(vertex, neighbor); + // Find out if the distance to the neighbor is shorter in this iteration + // then in previous one. + const distanceToVertex = distances[vertex.getKey()]; + const distanceToNeighbor = distanceToVertex + edge.weight; + if (distanceToNeighbor < distances[neighbor.getKey()]) { + distances[neighbor.getKey()] = distanceToNeighbor; + previousVertices[neighbor.getKey()] = vertex; + } + }); + }); + } + + return { + distances, + previousVertices, + }; +} diff --git a/src/algorithms/graph/dijkstra/__test__/dijkstra.test.js b/src/algorithms/graph/dijkstra/__test__/dijkstra.test.js index c4ce7af8..bdd75c52 100644 --- a/src/algorithms/graph/dijkstra/__test__/dijkstra.test.js +++ b/src/algorithms/graph/dijkstra/__test__/dijkstra.test.js @@ -61,5 +61,7 @@ describe('dijkstra', () => { expect(previousVertices.B.getKey()).toBe('A'); expect(previousVertices.G.getKey()).toBe('E'); expect(previousVertices.C.getKey()).toBe('A'); + expect(previousVertices.A).toBeNull(); + expect(previousVertices.H).toBeNull(); }); }); diff --git a/src/algorithms/graph/dijkstra/dijkstra.js b/src/algorithms/graph/dijkstra/dijkstra.js index 8d545ecc..3614376e 100644 --- a/src/algorithms/graph/dijkstra/dijkstra.js +++ b/src/algorithms/graph/dijkstra/dijkstra.js @@ -12,8 +12,9 @@ export default function dijkstra(graph, startVertex) { // Init all distances with infinity assuming that currently we can't reach // any of the vertices except start one. - Object.keys(graph.vertices).forEach((vertexKey) => { - distances[vertexKey] = Infinity; + graph.getAllVertices().forEach((vertex) => { + distances[vertex.getKey()] = Infinity; + previousVertices[vertex.getKey()] = null; }); distances[startVertex.getKey()] = 0; diff --git a/src/data-structures/graph/Graph.js b/src/data-structures/graph/Graph.js index fb047da1..cff44be8 100644 --- a/src/data-structures/graph/Graph.js +++ b/src/data-structures/graph/Graph.js @@ -32,6 +32,13 @@ export default class Graph { return vertex.getNeighbors(); } + /** + * @return {GraphVertex[]} + */ + getAllVertices() { + return Object.values(this.vertices); + } + /** * @param {GraphEdge} edge * @returns {Graph} diff --git a/src/data-structures/graph/__test__/Graph.test.js b/src/data-structures/graph/__test__/Graph.test.js index 494fbd14..a169de6b 100644 --- a/src/data-structures/graph/__test__/Graph.test.js +++ b/src/data-structures/graph/__test__/Graph.test.js @@ -28,6 +28,10 @@ describe('Graph', () => { graph.addEdge(edgeAB); + expect(graph.getAllVertices().length).toBe(2); + expect(graph.getAllVertices()[0]).toEqual(vertexA); + expect(graph.getAllVertices()[1]).toEqual(vertexB); + const graphVertexA = graph.findVertexByKey(vertexA.getKey()); const graphVertexB = graph.findVertexByKey(vertexB.getKey());