Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the implementation of the Edmond Karp algorithm along with test cases #252

Merged
merged 12 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions graph/edmonds_karp.ts
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]
mapcrafter2048 marked this conversation as resolved.
Show resolved Hide resolved
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
}
82 changes: 82 additions & 0 deletions graph/test/edmonds_karp.test.ts
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)
})
})
Loading