Skip to content

Commit 839efc7

Browse files
authored
feat: add bellman-ford algorithm (TheAlgorithms#136)
1 parent dba31e3 commit 839efc7

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

graph/bellman_ford.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @function bellmanFord
3+
* @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.
4+
* @Complexity_Analysis
5+
* Time complexity: O(E*V)
6+
* Space Complexity: O(V)
7+
* @param {[number, number][][]} graph - The graph in adjacency list form
8+
* @param {number} start - The source node
9+
* @return {number[] | undefined} - The shortest path to each node, undefined if there is negative weight cycle
10+
* @see https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm
11+
*/
12+
export const bellmanFord = (graph: [number, number][][], start: number): number[] | undefined => {
13+
// We save the shortest distance to each node in `distances`. If a node is
14+
// unreachable from the start node, its distance is Infinity.
15+
let distances = Array(graph.length).fill(Infinity);
16+
distances[start] = 0;
17+
18+
// On the i'th iteration, we compute all shortest paths that consists of i+1
19+
// nodes. If we compute this V-1 times, we will have computed all simple
20+
// shortest paths in the graph because a shortest path has at most V nodes.
21+
for (let i = 0; i < graph.length - 1; ++i) {
22+
for (let node = 0; node < graph.length; ++node) {
23+
for (const [child, weight] of graph[node]) {
24+
const new_distance = distances[node] + weight;
25+
if (new_distance < distances[child]) {
26+
distances[child] = new_distance
27+
}
28+
}
29+
}
30+
}
31+
32+
// Look through all edges. If the shortest path to a destination node d is
33+
// larger than the distance to source node s and weight(s->d), then the path
34+
// to s must have a negative weight cycle.
35+
for (let node = 0; node < graph.length; ++node) {
36+
for (const [child, weight] of graph[node]) {
37+
if (distances[child] > distances[node] + weight) {
38+
return undefined;
39+
}
40+
}
41+
}
42+
43+
return distances;
44+
}
45+

graph/test/bellman_ford.test.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { bellmanFord } from "../bellman_ford";
2+
3+
const init_graph = (N: number): [number, number][][] => {
4+
let graph = Array(N);
5+
for (let i = 0; i < N; ++i) {
6+
graph[i] = [];
7+
}
8+
return graph;
9+
}
10+
11+
describe("bellmanFord", () => {
12+
13+
const add_edge = (graph: [number, number][][], a: number, b: number, weight: number) => {
14+
graph[a].push([b, weight]);
15+
graph[b].push([a, weight]);
16+
}
17+
18+
it("should return the correct value", () => {
19+
let graph = init_graph(9);
20+
add_edge(graph, 0, 1, 4);
21+
add_edge(graph, 0, 7, 8);
22+
add_edge(graph, 1, 2, 8);
23+
add_edge(graph, 1, 7, 11);
24+
add_edge(graph, 2, 3, 7);
25+
add_edge(graph, 2, 5, 4);
26+
add_edge(graph, 2, 8, 2);
27+
add_edge(graph, 3, 4, 9);
28+
add_edge(graph, 3, 5, 14);
29+
add_edge(graph, 4, 5, 10);
30+
add_edge(graph, 5, 6, 2);
31+
add_edge(graph, 6, 7, 1);
32+
add_edge(graph, 6, 8, 6);
33+
add_edge(graph, 7, 8, 7);
34+
expect(bellmanFord(graph, 0)).toStrictEqual([0, 4, 12, 19, 21, 11, 9, 8, 14]);
35+
});
36+
37+
it("should return the correct value for single element graph", () => {
38+
expect(bellmanFord([[]], 0)).toStrictEqual([0]);
39+
});
40+
41+
let linear_graph = init_graph(4);
42+
add_edge(linear_graph, 0, 1, 1);
43+
add_edge(linear_graph, 1, 2, 2);
44+
add_edge(linear_graph, 2, 3, 3);
45+
test.each([[0, [0, 1, 3, 6]], [1, [1, 0, 2, 5]], [2, [3, 2, 0, 3]], [3, [6, 5, 3, 0]]])(
46+
"correct result for linear graph with source node %i",
47+
(source, result) => {
48+
expect(bellmanFord(linear_graph, source)).toStrictEqual(result);
49+
}
50+
);
51+
52+
let unreachable_graph = init_graph(3);
53+
add_edge(unreachable_graph, 0, 1, 1);
54+
test.each([[0, [0, 1, Infinity]], [1, [1, 0, Infinity]], [2, [Infinity, Infinity, 0]]])(
55+
"correct result for graph with unreachable nodes with source node %i",
56+
(source, result) => {
57+
expect(bellmanFord(unreachable_graph, source)).toStrictEqual(result);
58+
}
59+
);
60+
})
61+
62+
describe("bellmanFord negative cycle graphs", () => {
63+
it("should returned undefined for 2-node graph with negative cycle", () => {
64+
let basic = init_graph(2);
65+
basic[0].push([1, 2]);
66+
basic[1].push([0, -3]);
67+
expect(bellmanFord(basic, 0)).toStrictEqual(undefined);
68+
expect(bellmanFord(basic, 1)).toStrictEqual(undefined);
69+
});
70+
71+
it("should returned undefined for graph with negative cycle", () => {
72+
let negative = init_graph(5);
73+
negative[0].push([1, 6]);
74+
negative[0].push([3, 7]);
75+
negative[1].push([2, 5]);
76+
negative[1].push([3, 8]);
77+
negative[1].push([4, -4]);
78+
negative[2].push([1, -4]);
79+
negative[3].push([2, -3]);
80+
negative[3].push([4, 9]);
81+
negative[4].push([0, 3]);
82+
negative[4].push([2, 7]);
83+
for (let i = 0; i < 5; ++i) {
84+
expect(bellmanFord(negative, i)).toStrictEqual(undefined);
85+
}
86+
});
87+
});
88+

0 commit comments

Comments
 (0)