-
-
Notifications
You must be signed in to change notification settings - Fork 368
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added the implementation of the Edmond Karp algorithm along with test…
… cases (#252) * Added the implementation for bijection method binary to decimal and euler method * Added the implementation of the edmondkarp along with tests * Update graph/edmondkarp.ts Co-authored-by: Lars Müller <[email protected]> * Update edmondkarp.ts * Update graph/edmondkarp.ts Co-authored-by: Lars Müller <[email protected]> * Implement the furhter changes to make the algorithm more refined * Updated the test cases Updated the test cases * Updated the code and rewrite some functions Updated the code and rewrite some functions * Implement the optimal stack queue implementation in the edmond karp algorithm * removed the bisection_method.ts, decimal_convert.ts, euler_method.ts * Revert changes to package.json and package-lock.json --------- Co-authored-by: Lars Müller <[email protected]>
- Loading branch information
1 parent
44127b2
commit 19b4ced
Showing
2 changed files
with
179 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<number>() | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
}) | ||
}) |