-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add edmond-karp algorithm for max-flow (#712)
- Loading branch information
Showing
3 changed files
with
173 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
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,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 | ||
} |
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,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) | ||
} | ||
}) | ||
} | ||
} |