diff --git a/graph/edmondkarp.ts b/graph/edmondkarp.ts deleted file mode 100644 index ef51de73..00000000 --- a/graph/edmondkarp.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @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 2-tuple where the 0'th item is the destination node, and the 1'st item is the edge capacity. - * @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(V^2) where V is the number of vertices - * @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 - */ - -function edmondkarp(graph: [number, number][][], source: number, sink: number): number { - const n = graph.length; - - // Residual graph in adjacency list form - const residualGraph: Map = new Map(); - for (let u = 0; u < n; u++) { - residualGraph.set(u, []); - for (const [v, capacity] of graph[u]) { - residualGraph.get(u)?.push([v, capacity]); - if (!residualGraph.has(v)) { - residualGraph.set(v, []); - } - } - } - - const parent = Array(n).fill(null); - let maxFlow = 0; - - // Level-order BFS using two level arrays - const bfs = (): boolean => { - const visited = Array(n).fill(false); - const currentLevel: number[] = []; - const nextLevel: number[] = []; - currentLevel.push(source); - visited[source] = true; - - while (currentLevel.length > 0) { - nextLevel.length = 0; - - for (const u of currentLevel) { - for (const [v, capacity] of residualGraph.get(u)!) { - if (!visited[v] && capacity > 0) { - parent[v] = u; - visited[v] = true; - - // If we reach the sink, we have found an augmenting path - if (v === sink) { - return true; - } - - nextLevel.push(v); - } - } - } - - currentLevel.length = 0; - currentLevel.push(...nextLevel); - } - - return false; - }; - - while (bfs()) { - let pathFlow = Infinity; - - // Find the maximum flow through the path found - for (let v = sink; v !== source; v = parent[v]!) { - const u = parent[v]!; - const edge = residualGraph.get(u)!.find(([dest]) => dest === v)!; - pathFlow = Math.min(pathFlow, edge[1]); - } - - // Update the residual graph - for (let v = sink; v !== source; v = parent[v]!) { - const u = parent[v]!; - - // Update forward edge - const edgeIndex = residualGraph.get(u)!.findIndex(([dest]) => dest === v); - residualGraph.get(u)![edgeIndex][1] -= pathFlow; - - // Update backward edge - const reverseEdge = residualGraph.get(v)!.find(([dest]) => dest === u); - if (reverseEdge) { - reverseEdge[1] += pathFlow; - } else { - residualGraph.get(v)!.push([u, pathFlow]); - } - } - - maxFlow += pathFlow; - } - - return maxFlow; -} diff --git a/graph/edmonds_karp.ts b/graph/edmonds_karp.ts new file mode 100644 index 00000000..75fa163a --- /dev/null +++ b/graph/edmonds_karp.ts @@ -0,0 +1,96 @@ +/** + * @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: number[] = [] + queue.push(source) + visited[source] = true + parent[source] = null + + while (queue.length > 0) { + const u = queue.shift()! + 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.push(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 +}