diff --git a/graph/edmonds_karp.ts b/graph/edmonds_karp.ts new file mode 100644 index 00000000..fb781a42 --- /dev/null +++ b/graph/edmonds_karp.ts @@ -0,0 +1,97 @@ +import { StackQueue } from '../data_structures/queue/stack_queue' + +/** + * @function edmondsKarp + * @description Compute the maximum flow from a source node to a sink node using the Edmonds-Karp algorithm. + * @Complexity_Analysis + * Time complexity: O(V * E^2) where V is the number of vertices and E is the number of edges. + * Space Complexity: O(E) due to residual graph representation. + * @param {[number, number][][]} graph - The graph in adjacency list form. + * @param {number} source - The source node. + * @param {number} sink - The sink node. + * @return {number} - The maximum flow from the source node to the sink node. + * @see https://en.wikipedia.org/wiki/Edmonds%E2%80%93Karp_algorithm + */ +export default function edmondsKarp( + graph: [number, number][][], + source: number, + sink: number +): number { + const n = graph.length + + // Initialize residual graph + const residualGraph: [number, number][][] = Array.from( + { length: n }, + () => [] + ) + + // Build residual graph from the original graph + for (let u = 0; u < n; u++) { + for (const [v, cap] of graph[u]) { + if (cap > 0) { + residualGraph[u].push([v, cap]) // Forward edge + residualGraph[v].push([u, 0]) // Reverse edge with 0 capacity + } + } + } + + const findAugmentingPath = (parent: (number | null)[]): number => { + const visited = Array(n).fill(false) + const queue = new StackQueue() + queue.enqueue(source) + visited[source] = true + parent[source] = null + + while (queue.length() > 0) { + const u = queue.dequeue() + for (const [v, cap] of residualGraph[u]) { + if (!visited[v] && cap > 0) { + parent[v] = u + visited[v] = true + if (v === sink) { + // Return the bottleneck capacity along the path + let pathFlow = Infinity + let current = v + while (parent[current] !== null) { + const prev = parent[current]! + const edgeCap = residualGraph[prev].find( + ([node]) => node === current + )![1] + pathFlow = Math.min(pathFlow, edgeCap) + current = prev + } + return pathFlow + } + queue.enqueue(v) + } + } + } + return 0 + } + + let maxFlow = 0 + const parent = Array(n).fill(null) + + while (true) { + const pathFlow = findAugmentingPath(parent) + if (pathFlow === 0) break // No augmenting path found + + // Update the capacities and reverse capacities in the residual graph + let v = sink + while (parent[v] !== null) { + const u = parent[v]! + // Update capacity of the forward edge + const forwardEdge = residualGraph[u].find(([node]) => node === v)! + forwardEdge[1] -= pathFlow + // Update capacity of the reverse edge + const reverseEdge = residualGraph[v].find(([node]) => node === u)! + reverseEdge[1] += pathFlow + + v = u + } + + maxFlow += pathFlow + } + + return maxFlow +} diff --git a/graph/test/edmonds_karp.test.ts b/graph/test/edmonds_karp.test.ts new file mode 100644 index 00000000..22711ab9 --- /dev/null +++ b/graph/test/edmonds_karp.test.ts @@ -0,0 +1,82 @@ +import edmondsKarp from '../edmonds_karp' + +describe('Edmonds-Karp Algorithm', () => { + it('should find the maximum flow in a simple graph', () => { + const graph: [number, number][][] = [ + [ + [1, 3], + [2, 2] + ], // Node 0: Edges to node 1 (capacity 3), and node 2 (capacity 2) + [[3, 2]], // Node 1: Edge to node 3 (capacity 2) + [[3, 3]], // Node 2: Edge to node 3 (capacity 3) + [] // Node 3: No outgoing edges + ] + const source = 0 + const sink = 3 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(4) + }) + + it('should find the maximum flow in a more complex graph', () => { + const graph: [number, number][][] = [ + [ + [1, 10], + [2, 10] + ], // Node 0: Edges to node 1 and node 2 (both capacity 10) + [ + [3, 4], + [4, 8] + ], // Node 1: Edges to node 3 (capacity 4), and node 4 (capacity 8) + [[4, 9]], // Node 2: Edge to node 4 (capacity 9) + [[5, 10]], // Node 3: Edge to node 5 (capacity 10) + [[5, 10]], // Node 4: Edge to node 5 (capacity 10) + [] // Node 5: No outgoing edges (sink) + ] + const source = 0 + const sink = 5 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(14) + }) + + it('should return 0 when there is no path from source to sink', () => { + const graph: [number, number][][] = [ + [], // Node 0: No outgoing edges + [], // Node 1: No outgoing edges + [] // Node 2: No outgoing edges (sink) + ] + const source = 0 + const sink = 2 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(0) + }) + + it('should handle graphs with no edges', () => { + const graph: [number, number][][] = [ + [], // Node 0: No outgoing edges + [], // Node 1: No outgoing edges + [] // Node 2: No outgoing edges + ] + const source = 0 + const sink = 2 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(0) + }) + + it('should handle graphs with self-loops', () => { + const graph: [number, number][][] = [ + [ + [0, 10], + [1, 10] + ], // Node 0: Self-loop with capacity 10, and edge to node 1 (capacity 10) + [ + [1, 10], + [2, 10] + ], // Node 1: Self-loop and edge to node 2 + [] // Node 2: No outgoing edges (sink) + ] + const source = 0 + const sink = 2 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(10) + }) +})