This commit is contained in:
tobiasloader.eth 2024-07-14 15:48:46 +01:00 committed by GitHub
commit d1f599332d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 357 additions and 0 deletions

View File

@ -0,0 +1,19 @@
# Edmonds-Karp
The Edmonds-Karp algorithm in an implementation of the [Ford-Fulkerson method](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/ford-fulkerson) for finding the maximum flow in a network in *O(|V||E|<sup>2</sup>)* time.
Searching for an augmenting path P in the residual graph *G<sub>f</sub>* is done by [breadth-first search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/breadth-first-search). This is in contrast with Ford-Fulkerson where the search order is not specified.
![Edmonds-Karp Example 0](https://upload.wikimedia.org/wikipedia/commons/3/3e/Edmonds-Karp_flow_example_0.svg)
![Edmonds-Karp Example 1](https://upload.wikimedia.org/wikipedia/commons/6/6d/Edmonds-Karp_flow_example_1.svg)
![Edmonds-Karp Example 2](https://upload.wikimedia.org/wikipedia/commons/c/c1/Edmonds-Karp_flow_example_2.svg)
![Edmonds-Karp Example 3](https://upload.wikimedia.org/wikipedia/commons/a/a5/Edmonds-Karp_flow_example_3.svg)
![Edmonds-Karp Example 4](https://upload.wikimedia.org/wikipedia/commons/b/bd/Edmonds-Karp_flow_example_4.svg)
## References
- [Wikipedia | Edmonds-Karp](https://en.wikipedia.org/wiki/Edmonds%E2%80%93Karp_algorithm)
- [Youtube | Edmonds-Karp Algorithm](https://www.youtube.com/watch?v=RppuJYwlcI8)
- [Wikibooks | Java Implementation](https://en.wikibooks.org/wiki/Algorithm_Implementation/Graphs/Maximum_flow/Edmonds-Karp)
- [Cornell University | Edmonds-Karp](https://www.cs.cornell.edu/courses/cs4820/2012sp/handouts/edmondskarp.pdf)
- [Wikipedia Commons | Edmonds-Karp Examples](https://commons.wikimedia.org/wiki/File:Edmonds-Karp_flow_example_0.svg)

View File

@ -0,0 +1,58 @@
import GraphVertex from '../../../../../data-structures/graph/GraphVertex';
import GraphEdge from '../../../../../data-structures/graph/GraphEdge';
import Graph from '../../../../../data-structures/graph/Graph';
import edmondsKarp from '../edmondsKarp';
describe('edmondsKarp', () => {
it('should solve the maximum flow problem', () => {
const capacities = {
S_A: 3, S_B: 3, A_B: 4, A_D: 3, B_C: 5, B_D: 3, C_T: 2, D_T: 4,
};
const vertexS = new GraphVertex('S');
const vertexA = new GraphVertex('A');
const vertexB = new GraphVertex('B');
const vertexC = new GraphVertex('C');
const vertexD = new GraphVertex('D');
const vertexT = new GraphVertex('T');
const edgeSA = new GraphEdge(vertexS, vertexA, 0);
const edgeSB = new GraphEdge(vertexS, vertexB, 0);
const edgeAB = new GraphEdge(vertexA, vertexB, 0);
const edgeAD = new GraphEdge(vertexA, vertexD, 0);
const edgeBC = new GraphEdge(vertexB, vertexC, 0);
const edgeBD = new GraphEdge(vertexB, vertexD, 0);
const edgeCT = new GraphEdge(vertexC, vertexT, 0);
const edgeDT = new GraphEdge(vertexD, vertexT, 0);
const graph = new Graph(true);
graph
.addEdge(edgeSA)
.addEdge(edgeSB)
.addEdge(edgeAB)
.addEdge(edgeAD)
.addEdge(edgeBC)
.addEdge(edgeBD)
.addEdge(edgeCT)
.addEdge(edgeDT);
// COMPUTE edmondsKarp
const edmondsKarpObj = edmondsKarp(graph, vertexS, vertexT, capacities);
// max flow is 6 as expected
expect(edmondsKarpObj.maxflow).toEqual(6);
// flow through the network is as expected
const flowThroughEachEdge = {};
const expectedFlow = {
S_A: 3, S_B: 3, A_B: 0, A_D: 3, B_C: 2, B_D: 1, C_T: 2, D_T: 4,
};
edmondsKarpObj.graph.getAllEdges().forEach((e) => {
flowThroughEachEdge[e.getKey()] = e.weight;
});
expect(JSON.stringify(flowThroughEachEdge)).toBe(JSON.stringify(expectedFlow));
});
});

View File

@ -0,0 +1,213 @@
import GraphVertex from '../../../../data-structures/graph/GraphVertex';
import GraphEdge from '../../../../data-structures/graph/GraphEdge';
import Graph from '../../../../data-structures/graph/Graph';
import breadthFirstSearch from '../../breadth-first-search/breadthFirstSearch';
import Queue from '../../../../data-structures/queue/Queue';
/**
* Edmonds-Karp implementation
*
* @param {Graph} graph
* @param {GraphVertex} source
* @param {GraphVertex} sink
* @param {object} capacities
* @return {object}
*
* NB: capacities is a mapping from: GraphEdge.getKey() to number
* & return is {graph:graph,maxflow:f} of types {Graph, Number}
*
*/
export default function edmondsKarp(graph, source, sink, capacities) {
// variable to hold the max flow
// f gets incremented in 'augment(path)'
let f = 0;
// variable to hold the flow network
// the weights of the edges of g are the flow on those edges
const g = graph;
// set capacities where not defined to be infinity
const cs = capacities;
g.getAllEdges().forEach((edge) => {
if (!(edge.getKey() in capacities)) cs[edge.getKey()] = Number.POSITIVE_INFINITY;
});
// the network flow is defined as the weights of g
// assert the edges have 0 weight initally so that flow can be set to 0
g.getAllEdges().forEach((edge) => {
if (edge.weight !== 0) throw Error('ASSERT: the edges of g should have 0 weight the edges represent the flow FordFulkerson asserts 0 inital flow in network');
});
// initialise residual graph
// build is done in function buildResidualGraph
let residualGraph;
// FLOW HELPER functions
const getFlow = (edgeKey) => g.edges[edgeKey].weight;
const updateFlow = (edgeKey, value) => { g.edges[edgeKey].weight += value; };
// RESIDUAL CAPACITY HELPER function
const getResidualCapacity = (edgeKey) => residualGraph.edges[edgeKey].weight;
// FUNCTION for building Residual Graph
// -> And hence determining residual capacities
function buildResidualGraph() {
// the weights of the residualGraph are the residual capacities
residualGraph = new Graph(true);
// add to the residualGraph all the vertices in g
Object.keys(g.getVerticesIndices()).forEach((key) => {
residualGraph.addVertex(new GraphVertex(key));
});
// compute residual capacities and assign as weights to residualGraph
g.getAllEdges().forEach((edge) => {
// edge is an edge in g
// get edgeKey for extracting edge capacity from capacities
const edgeKey = edge.getKey();
// get the residualGraph start and end vertices (from edge in g)
const startVertex = residualGraph.getVertexByKey(edge.startVertex.getKey());
const endVertex = residualGraph.getVertexByKey(edge.endVertex.getKey());
// if the flow is less than the capacity:
if (getFlow(edgeKey) < cs[edgeKey]) {
// compute the residual capacity
const capMinusFlow = cs[edgeKey] - getFlow(edgeKey);
// add a forward edge to residualGraph of the difference (cap-flow)
// -> because (cap-flow) units of flow can still be pushed down the edge
residualGraph.addEdge(new GraphEdge(startVertex, endVertex, capMinusFlow));
}
// if the flow is non zero
if (getFlow(edgeKey) > 0) {
// add a backwards edge to residualGraph with weight of flow
// -> so that all of the flow along edge can be undone
residualGraph.addEdge(new GraphEdge(endVertex, startVertex, getFlow(edgeKey)));
}
});
// return residualGraph;
}
/**
* Augment: augments the flow network given a path of edges in residualGraph
* @param {GraphEdge[]} edgePath
*
* Considers the path in ResidualCap
*/
function augment(edgePath) {
// compute the bottleneck capacity of the path in residualGraph
let bottleneck = Number.POSITIVE_INFINITY;
edgePath.forEach((edge) => {
// get the residual capacity of an edge
const resCap = getResidualCapacity(edge.getKey());
// if the residual capacity is less than bottleneck than set bottleneck
if (resCap < bottleneck) bottleneck = resCap;
});
edgePath.forEach((edgeResidualGraph) => {
// get the vertices in g corresponding to the edge in residualGraph
const startVertex = g.getVertexByKey(edgeResidualGraph.startVertex.getKey());
const endVertex = g.getVertexByKey(edgeResidualGraph.endVertex.getKey());
// if it contains a forward edge, update the flow by adding bottleneck
const forwardEdge = g.findEdge(startVertex, endVertex);
if (forwardEdge !== null) updateFlow(forwardEdge.getKey(), bottleneck);
// if it contains a backward edge, update the flow by adding -bottleneck
const backwardEdge = g.findEdge(endVertex, startVertex);
if (backwardEdge !== null) updateFlow(backwardEdge.getKey(), -bottleneck);
});
// increase the value of flow by bottleneck
f += bottleneck;
}
// intialise the variable augmentingPath
let augmentingPath;
/**
* findAugmentingPath: finds an augmenting path in residualGraph
* Uses BFS to determine the path (Edmonds-Karp specification)
*/
function findAugmentingPath() {
// intialise augmentingPath
augmentingPath = [];
// get the source and sink keys
const s = source.getKey();
const t = sink.getKey();
// initialise a queue of vertices visited
const vertexPathQ = new Queue();
vertexPathQ.enqueue([s]);
// initialise the currentPath array and seen object
let currentPath = [];
const seen = {};
// initialise variable found has sink been found
let found = false;
// Specify BFS traversal callbacks.
const bfsCallbacks = {
enterVertex: () => {
// on entering vertex dequeue from vertex queue
currentPath = vertexPathQ.dequeue();
},
allowTraversal: ({ nextVertex }) => {
// if sink has been found
if (found) return false;
// if the next vertex has been seen stop traversing branch
if (seen[nextVertex.getKey()]) return false;
// otherwise add next vertex to the seen object (if it is not sink)
if (nextVertex.getKey() !== t) seen[nextVertex.getKey()] = true;
// compute thisPath from currentPath and nextVertex
const thisPath = [];
currentPath.forEach((v) => thisPath.push(v));
thisPath.push(nextVertex.getKey());
// enqueue thisPath to vertexPathQ
vertexPathQ.enqueue(thisPath);
// if nextVertex is the sink then found is true
if (nextVertex.getKey() === t) found = true;
// continue traversal if not found
return !found;
},
};
// perform breadth first search to find a vertex path from s to t
breadthFirstSearch(residualGraph, residualGraph.getVertexByKey(s), bfsCallbacks);
// the search path is the next of vertexPathQ to dequeue
const vertexPath = vertexPathQ.dequeue();
// check that vertexPath exists and ends with the sink
if (vertexPath !== null && vertexPath[vertexPath.length - 1] === t) {
// reset augmentingPath
augmentingPath = [];
for (let i = 0; i < vertexPath.length - 1; i += 1) {
// get the start and end vertices for next edge in path in residualGraph
const startVertex = residualGraph.getVertexByKey(vertexPath[i]);
const endVertex = residualGraph.getVertexByKey(vertexPath[i + 1]);
// find the edge in residualGraph
const edge = residualGraph.findEdge(startVertex, endVertex);
// add the edge to the augmentingPath
augmentingPath.push(edge);
}
// an augmenting path has been found so return true
return true;
}
// otherwise no augmenting path found so return false
return false;
}
/// --- MAIN EXECUTION LOOP ---
// build an inital residual graph
buildResidualGraph();
// while there is still an augmenting path in residualGraph
while (findAugmentingPath()) {
// augment the flow network along that graph
augment(augmentingPath);
// then build the updated residual graph
buildResidualGraph();
}
// g is the final graph with flow values as weights on the edges.
// f is the max flow.
return { graph: g, maxflow: f };
}

View File

@ -0,0 +1,67 @@
# Ford-Fulkerson
The FordFulkerson method is a greedy algorithm that computes the maximum flow in a flow network. It is sometimes called a "method" instead of an "algorithm" as the approach to finding augmenting paths in a residual graph is not fully specified.
To see a Javascript implementation, go to the [Edmonds-Karp](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/edmonds-karp) algorithm.
Edmonds-Karp is an implementation of Ford-Fulkerson that specifies the use of [breadth-first search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/breadth-first-search) (BFS) to determine the search order for an augmenting path.
### Residual Graph G<sub>f</sub>
Construction of a residual graph G<sub>f</sub> = ( V , E<sub>f</sub> ) is dependent on a flow `f` (and the graph `G` and its flow capacity `c`). Initialised at the start of the Ford-Fulkerson algorithm, it is updated at every pass of the main loop. It maintains the residual capacity c<sub>f</sub>(e) and edge set E<sub>f</sub> that depend on the flow `f`.
**Residual Graph Construction:**
Start with empty E<sub>f</sub> and c<sub>f</sub>(e). For each edge `e = (u,v)` with flow `f(e)` and capacity *c(e)*:
- if `f(e) < c(e)`: add *forward* edge e to E<sub>f</sub> with capacity *c<sub>f</sub>(e)* = `c(e) - f(e)`
- if `f(e) > 0`: add *backward* edge *e'=(v,u)* to E<sub>f</sub> with capacity *c<sub>f</sub>(e')* = `f(e)`
**Residual Graph Intuition:**
- when the flow through an edge is less than its capacity, you can still *push* at most the difference `c(e) - f(e) units of flow` along that edge
- when the flow through an edge is non-zero, you can *undo* all of the flow `f(e) units of flow` along that edge
### Pseudocode algorithm
**Algorithm**: Ford-Fulkerson
**Inputs:** a network `G = ( V, E )` with flow capacity `c`, source `s`, sink `t`
**Outputs:** the maximum value of the flow `|f|` from `s` to `t`
```pseudocode
FORD-FULKERSON(G,s,t,c)
AUGMENT(f,P)
bottleneck_cap_p = min {c_f(e) for e in P}
f2(edge) = f(edge) for edge in E
for edge in E
if edge in path P
f2(edge) = f(edge) + bottleneck_cap_p
if reverse(edge) in path P
f2(edge) = f(edge) - bottleneck_cap_p
return f2
f(edge) = 0 for edge in E
G_f = residual graph of G (with respect to f and capacity c)
while there exists a path P (from s to t) in G_f
f = AUGMENT(f,P)
Update G_f (by definition of residual graph with new f)
return f
```
### Augmenting along a path
Augmenting along a path `P` always adds `min {c_f(e) for e in P}` to the flow through the network. `P` is any path from `s` to `t` in the residual graph *G<sub>f</sub>*.
### Demo of the algorithm working
![Ford-Fulkerson Demo](https://upload.wikimedia.org/wikipedia/commons/a/ad/FordFulkersonDemo.gif)
## References
- [Wikipedia | Ford-Fulkerson](https://en.wikipedia.org/wiki/Ford-Fulkerson_algorithm)
- [Wikipedia | Maximum Flow](https://en.wikipedia.org/wiki/Maximum_flow_problem)
- [Youtube | Max Flow Ford-Fulkerson](https://www.youtube.com/watch?v=LdOnanfc5TM)
- [Amazon | Introduction to Algorithms (CLRS)](https://www.amazon.co.uk/Introduction-Algorithms-Thomas-H-Cormen/dp/0262033844)
- [Wikipedia Commons | Ford-Fulkerson Demo](https://commons.wikimedia.org/wiki/File:FordFulkersonDemo.gif)