Skip to content

Commit

Permalink
feat: add edmond-karp algorithm for max-flow (#712)
Browse files Browse the repository at this point in the history
  • Loading branch information
jafar75 authored Mar 28, 2024
1 parent a938a23 commit cbaed23
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute.
12. [`NewUnionFind`](./graph/unionfind.go#L24): Initialise a new union find data structure with s nodes
13. [`NotExist`](./graph/depthfirstsearch.go#L12): No description provided.
14. [`Topological`](./graph/topological.go#L7): Topological assumes that graph given is valid and that its possible to get a topological ordering. constraints are array of []int{a, b}, representing an edge going from a to b
15. [`Edmond-Karp`](./graph/edmondkarp.go#L43): Computes the maximum possible flow between a pair of s-t vertices in a weighted graph

---
##### Types
Expand Down
93 changes: 93 additions & 0 deletions graph/edmondkarp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Edmond-Karp algorithm is an implementation of the Ford-Fulkerson method
// to compute max-flow between a pair of source-sink vertices in a weighted graph
// It uses BFS (Breadth First Search) to find the residual paths
// Time Complexity: O(V * E^2) where V is the number of vertices and E is the number of edges
// Space Complexity: O(V + E) Because we keep residual graph in size of the original graph
// Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein. 2009. Introduction to Algorithms, Third Edition (3rd. ed.). The MIT Press.

package graph

import (
"math"
)

// Returns a mapping of vertices as path, if there is any from source to sink
// Otherwise, returns nil
func FindPath(rGraph WeightedGraph, source int, sink int) map[int]int {
queue := make([]int, 0)
marked := make([]bool, len(rGraph))
marked[source] = true
queue = append(queue, source)
parent := make(map[int]int)

// BFS loop with saving the path found
for len(queue) > 0 {
v := queue[0]
queue = queue[1:]
for i := 0; i < len(rGraph[v]); i++ {
if !marked[i] && rGraph[v][i] > 0 {
parent[i] = v
// Terminate the BFS, if we reach to sink
if i == sink {
return parent
}
marked[i] = true
queue = append(queue, i)
}
}
}
// source and sink are not in the same connected component
return nil
}

func EdmondKarp(graph WeightedGraph, source int, sink int) float64 {
// Check graph emptiness
if len(graph) == 0 {
return 0.0
}

// Check correct dimensions of the graph slice
for i := 0; i < len(graph); i++ {
if len(graph[i]) != len(graph) {
return 0.0
}
}

rGraph := make(WeightedGraph, len(graph))
for i := 0; i < len(graph); i++ {
rGraph[i] = make([]float64, len(graph))
}
// Init the residual graph with the same capacities as the original graph
copy(rGraph, graph)

maxFlow := 0.0

for {
parent := FindPath(rGraph, source, sink)
if parent == nil {
break
}
// Finding the max flow over the path returned by BFS
// i.e. finding minimum residual capacity amonth the path edges
pathFlow := math.MaxFloat64
for v := sink; v != source; v = parent[v] {
u := parent[v]
if rGraph[u][v] < pathFlow {
pathFlow = rGraph[u][v]
}
}

// update residual capacities of the edges and
// reverse edges along the path
for v := sink; v != source; v = parent[v] {
u := parent[v]
rGraph[u][v] -= pathFlow
rGraph[v][u] += pathFlow
}

// Update the total flow found so far
maxFlow += pathFlow
}

return maxFlow
}
79 changes: 79 additions & 0 deletions graph/edmondkarp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package graph

import (
"testing"
)

func TestEdmondKarp(t *testing.T) {
var edmondKarpTestData = []struct {
description string
graph WeightedGraph
source int
sink int
expected float64
}{
{
description: "test empty graph",
graph: nil,
source: 0,
sink: 0,
expected: 0.0,
},
{
description: "test graph with wrong dimensions",
graph: WeightedGraph{
{1, 2},
{0},
},
source: 0,
sink: 1,
expected: 0.0,
},
{
description: "test graph with no edges",
graph: WeightedGraph{
{0, 0},
{0, 0},
},
source: 0,
sink: 1,
expected: 0.0,
},
{
description: "test graph with 4 vertices",
graph: WeightedGraph{
{0, 1000000, 1000000, 0},
{0, 0, 1, 1000000},
{0, 0, 0, 1000000},
{0, 0, 0, 0},
},
source: 0,
sink: 3,
expected: 2000000,
},
{
description: "test graph with 6 vertices and some float64 weights",
graph: WeightedGraph{
{0, 16, 13.8, 0, 0, 0},
{0, 0, 10, 12.7, 0, 0},
{0, 4.2, 0, 0, 14, 0},
{0, 0, 9.1, 0, 0, 21.3},
{0, 0, 0, 7.5, 0, 4},
{0, 0, 0, 0, 0, 0},
},
source: 0,
sink: 5,
expected: 24.2,
},
}
for _, test := range edmondKarpTestData {
t.Run(test.description, func(t *testing.T) {
result := EdmondKarp(test.graph, test.source, test.sink)

if !almostEqual(test.expected, result) {
t.Logf("FAIL: %s", test.description)
t.Fatalf("Expected result:%f\nFound: %f", test.expected, result)
}
})
}
}

0 comments on commit cbaed23

Please sign in to comment.