Skip to content

Commit a358ea6

Browse files
Added the implementation of the edmondkarp along with tests
1 parent 9b477f1 commit a358ea6

File tree

2 files changed

+160
-0
lines changed

2 files changed

+160
-0
lines changed

graph/edmondkarp.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* @function edmondkarp
3+
* @description Compute the maximum flow from a source node to a sink node. 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 3-tuple where the 0'th item is the destination node, the 1'th item is the edge weight, and the 2'nd item is the edge capacity.
4+
* @Complexity_Analysis
5+
* Time complexity: O(V*E^2) where V is the number of vertices and E is the number of edges
6+
* Space Complexity: O(V) where V is the number of vertices
7+
* @param {[number, number, number][][]} graph - The graph in adjacency list form
8+
* @param {number} source - The source node
9+
* @param {number} sink - The sink node
10+
* @return {number} - The maximum flow from the source node to the sink node
11+
* @see https://en.wikipedia.org/wiki/Edmonds%E2%80%93Karp_algorithm
12+
*/
13+
14+
function edmondkarp(graph: [number, number, number][][], source: number, sink: number): number {
15+
// Initialize capacity and flow matrices with zeros and build capacity matrix from graph
16+
const n = graph.length;
17+
const capacity = Array.from({ length: n }, () => Array(n).fill(0));
18+
const flow = Array.from({ length: n }, () => Array(n).fill(0));
19+
20+
// Build capacity matrix
21+
for (let u = 0; u < n; u++) {
22+
for (const [v, , cap] of graph[u]) {
23+
capacity[u][v] = cap;
24+
}
25+
}
26+
27+
// Breadth-first search
28+
const bfs = (parent: number[]): boolean => {
29+
const visited = Array(n).fill(false);
30+
const queue: number[] = [];
31+
queue.push(source);
32+
visited[source] = true;
33+
34+
// Find an augmenting path from source to sink by doing a BFS traversal
35+
while (queue.length > 0) {
36+
// Dequeue
37+
const u = queue.shift()!;
38+
// Enqueue all adjacent unvisited vertices with available capacity
39+
for (let v = 0; v < n; v++) {
40+
// If there is available capacity and the vertex has not been visited
41+
if (!visited[v] && capacity[u][v] - flow[u][v] > 0) {
42+
queue.push(v);
43+
visited[v] = true;
44+
parent[v] = u;
45+
// If we reach the sink, we have found the augmenting path
46+
if (v === sink) {
47+
return true;
48+
}
49+
}
50+
}
51+
}
52+
return false;
53+
};
54+
55+
let maxFlow = 0;
56+
const parent = Array(n).fill(-1);
57+
58+
while (bfs(parent)) {
59+
let pathFlow = Infinity;
60+
// Find the maximum flow through the path found
61+
for (let v = sink; v !== source; v = parent[v]) {
62+
const u = parent[v];
63+
pathFlow = Math.min(pathFlow, capacity[u][v] - flow[u][v]);
64+
}
65+
// Update the flow matrix
66+
for (let v = sink; v !== source; v = parent[v]) {
67+
const u = parent[v];
68+
flow[u][v] += pathFlow;
69+
flow[v][u] -= pathFlow;
70+
}
71+
72+
maxFlow += pathFlow;
73+
}
74+
75+
return maxFlow;
76+
}

graph/test/edmondkarp_test.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { edmondsKarp } from '../edmondsKarp'
2+
3+
describe('edmondsKarp', () => {
4+
const init_flow_network = (N: number): number[][] => {
5+
const graph = Array.from({ length: N }, () => Array(N).fill(0));
6+
return graph;
7+
}
8+
9+
const add_capacity = (
10+
graph: number[][],
11+
u: number,
12+
v: number,
13+
capacity: number
14+
) => {
15+
graph[u][v] = capacity;
16+
}
17+
18+
it('should return the correct maximum flow value for basic graph', () => {
19+
const graph = init_flow_network(6);
20+
add_capacity(graph, 0, 1, 16);
21+
add_capacity(graph, 0, 2, 13);
22+
add_capacity(graph, 1, 2, 10);
23+
add_capacity(graph, 1, 3, 12);
24+
add_capacity(graph, 2, 1, 4);
25+
add_capacity(graph, 2, 4, 14);
26+
add_capacity(graph, 3, 2, 9);
27+
add_capacity(graph, 3, 5, 20);
28+
add_capacity(graph, 4, 3, 7);
29+
add_capacity(graph, 4, 5, 4);
30+
expect(edmondsKarp(graph, 0, 5)).toBe(23);
31+
});
32+
33+
it('should return the correct maximum flow value for single element graph', () => {
34+
const graph = init_flow_network(1);
35+
expect(edmondsKarp(graph, 0, 0)).toBe(0);
36+
});
37+
38+
const linear_flow_network = init_flow_network(4);
39+
add_capacity(linear_flow_network, 0, 1, 10);
40+
add_capacity(linear_flow_network, 1, 2, 5);
41+
add_capacity(linear_flow_network, 2, 3, 15);
42+
test.each([
43+
[0, 3, 5],
44+
[0, 2, 5],
45+
[1, 3, 5],
46+
[1, 2, 5],
47+
])(
48+
'correct result for linear flow network with source node %i and sink node %i',
49+
(source, sink, maxFlow) => {
50+
expect(edmondsKarp(linear_flow_network, source, sink)).toBe(maxFlow);
51+
}
52+
);
53+
54+
const disconnected_flow_network = init_flow_network(4);
55+
add_capacity(disconnected_flow_network, 0, 1, 10);
56+
add_capacity(disconnected_flow_network, 2, 3, 5);
57+
test.each([
58+
[0, 3, 0],
59+
[1, 2, 0],
60+
[2, 3, 5],
61+
])(
62+
'correct result for disconnected flow network with source node %i and sink node %i',
63+
(source, sink, maxFlow) => {
64+
expect(edmondsKarp(disconnected_flow_network, source, sink)).toBe(maxFlow);
65+
}
66+
);
67+
68+
const cyclic_flow_network = init_flow_network(5);
69+
add_capacity(cyclic_flow_network, 0, 1, 10);
70+
add_capacity(cyclic_flow_network, 1, 2, 5);
71+
add_capacity(cyclic_flow_network, 2, 0, 7);
72+
add_capacity(cyclic_flow_network, 2, 3, 10);
73+
add_capacity(cyclic_flow_network, 3, 4, 10);
74+
test.each([
75+
[0, 4, 10],
76+
[1, 4, 10],
77+
[2, 4, 10],
78+
])(
79+
'correct result for cyclic flow network with source node %i and sink node %i',
80+
(source, sink, maxFlow) => {
81+
expect(edmondsKarp(cyclic_flow_network, source, sink)).toBe(maxFlow);
82+
}
83+
);
84+
});

0 commit comments

Comments
 (0)