diff --git a/README.md b/README.md index a7ef32fb5..b55033399 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/graph/edmondkarp.go b/graph/edmondkarp.go new file mode 100644 index 000000000..f6917efcb --- /dev/null +++ b/graph/edmondkarp.go @@ -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 +} diff --git a/graph/edmondkarp_test.go b/graph/edmondkarp_test.go new file mode 100644 index 000000000..08e104652 --- /dev/null +++ b/graph/edmondkarp_test.go @@ -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) + } + }) + } +}