diff --git a/graph/bellman_ford.ts b/graph/bellman_ford.ts new file mode 100644 index 00000000..70e326d2 --- /dev/null +++ b/graph/bellman_ford.ts @@ -0,0 +1,45 @@ +/** + * @function bellmanFord + * @description Compute the shortest path from a source node to all other nodes. If there is negative weight cycle, returns undefined. The input graph is in adjacency list form. It is a multidimensional array of edges. graph[i] holds the edges for the i'th node. Each edge is a 2-tuple where the 0'th item is the destination node, and the 1'th item is the edge weight. + * @Complexity_Analysis + * Time complexity: O(E*V) + * Space Complexity: O(V) + * @param {[number, number][][]} graph - The graph in adjacency list form + * @param {number} start - The source node + * @return {number[] | undefined} - The shortest path to each node, undefined if there is negative weight cycle + * @see https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm + */ +export const bellmanFord = (graph: [number, number][][], start: number): number[] | undefined => { + // We save the shortest distance to each node in `distances`. If a node is + // unreachable from the start node, its distance is Infinity. + let distances = Array(graph.length).fill(Infinity); + distances[start] = 0; + + // On the i'th iteration, we compute all shortest paths that consists of i+1 + // nodes. If we compute this V-1 times, we will have computed all simple + // shortest paths in the graph because a shortest path has at most V nodes. + for (let i = 0; i < graph.length - 1; ++i) { + for (let node = 0; node < graph.length; ++node) { + for (const [child, weight] of graph[node]) { + const new_distance = distances[node] + weight; + if (new_distance < distances[child]) { + distances[child] = new_distance + } + } + } + } + + // Look through all edges. If the shortest path to a destination node d is + // larger than the distance to source node s and weight(s->d), then the path + // to s must have a negative weight cycle. + for (let node = 0; node < graph.length; ++node) { + for (const [child, weight] of graph[node]) { + if (distances[child] > distances[node] + weight) { + return undefined; + } + } + } + + return distances; +} + diff --git a/graph/test/bellman_ford.test.ts b/graph/test/bellman_ford.test.ts new file mode 100644 index 00000000..77928a63 --- /dev/null +++ b/graph/test/bellman_ford.test.ts @@ -0,0 +1,88 @@ +import { bellmanFord } from "../bellman_ford"; + +const init_graph = (N: number): [number, number][][] => { + let graph = Array(N); + for (let i = 0; i < N; ++i) { + graph[i] = []; + } + return graph; +} + +describe("bellmanFord", () => { + + const add_edge = (graph: [number, number][][], a: number, b: number, weight: number) => { + graph[a].push([b, weight]); + graph[b].push([a, weight]); + } + + it("should return the correct value", () => { + let graph = init_graph(9); + add_edge(graph, 0, 1, 4); + add_edge(graph, 0, 7, 8); + add_edge(graph, 1, 2, 8); + add_edge(graph, 1, 7, 11); + add_edge(graph, 2, 3, 7); + add_edge(graph, 2, 5, 4); + add_edge(graph, 2, 8, 2); + add_edge(graph, 3, 4, 9); + add_edge(graph, 3, 5, 14); + add_edge(graph, 4, 5, 10); + add_edge(graph, 5, 6, 2); + add_edge(graph, 6, 7, 1); + add_edge(graph, 6, 8, 6); + add_edge(graph, 7, 8, 7); + expect(bellmanFord(graph, 0)).toStrictEqual([0, 4, 12, 19, 21, 11, 9, 8, 14]); + }); + + it("should return the correct value for single element graph", () => { + expect(bellmanFord([[]], 0)).toStrictEqual([0]); + }); + + let linear_graph = init_graph(4); + add_edge(linear_graph, 0, 1, 1); + add_edge(linear_graph, 1, 2, 2); + add_edge(linear_graph, 2, 3, 3); + test.each([[0, [0, 1, 3, 6]], [1, [1, 0, 2, 5]], [2, [3, 2, 0, 3]], [3, [6, 5, 3, 0]]])( + "correct result for linear graph with source node %i", + (source, result) => { + expect(bellmanFord(linear_graph, source)).toStrictEqual(result); + } + ); + + let unreachable_graph = init_graph(3); + add_edge(unreachable_graph, 0, 1, 1); + test.each([[0, [0, 1, Infinity]], [1, [1, 0, Infinity]], [2, [Infinity, Infinity, 0]]])( + "correct result for graph with unreachable nodes with source node %i", + (source, result) => { + expect(bellmanFord(unreachable_graph, source)).toStrictEqual(result); + } + ); +}) + +describe("bellmanFord negative cycle graphs", () => { + it("should returned undefined for 2-node graph with negative cycle", () => { + let basic = init_graph(2); + basic[0].push([1, 2]); + basic[1].push([0, -3]); + expect(bellmanFord(basic, 0)).toStrictEqual(undefined); + expect(bellmanFord(basic, 1)).toStrictEqual(undefined); + }); + + it("should returned undefined for graph with negative cycle", () => { + let negative = init_graph(5); + negative[0].push([1, 6]); + negative[0].push([3, 7]); + negative[1].push([2, 5]); + negative[1].push([3, 8]); + negative[1].push([4, -4]); + negative[2].push([1, -4]); + negative[3].push([2, -3]); + negative[3].push([4, 9]); + negative[4].push([0, 3]); + negative[4].push([2, 7]); + for (let i = 0; i < 5; ++i) { + expect(bellmanFord(negative, i)).toStrictEqual(undefined); + } + }); +}); +