From ecac9b9f820607789240c93f0c0b9d6c0f0415de Mon Sep 17 00:00:00 2001 From: Toby Date: Sat, 8 Apr 2023 18:43:00 +0100 Subject: [PATCH 1/3] add Edmonds Karp and readme for Ford Fulkerson --- .../flow-networks/edmonds-karp/README.md | 19 ++ .../edmonds-karp/__test__/edmondsKarp.test.js | 52 +++++ .../flow-networks/edmonds-karp/edmondsKarp.js | 197 ++++++++++++++++++ .../flow-networks/ford-fulkerson/README.md | 67 ++++++ 4 files changed, 335 insertions(+) create mode 100644 src/algorithms/graph/flow-networks/edmonds-karp/README.md create mode 100644 src/algorithms/graph/flow-networks/edmonds-karp/__test__/edmondsKarp.test.js create mode 100644 src/algorithms/graph/flow-networks/edmonds-karp/edmondsKarp.js create mode 100644 src/algorithms/graph/flow-networks/ford-fulkerson/README.md diff --git a/src/algorithms/graph/flow-networks/edmonds-karp/README.md b/src/algorithms/graph/flow-networks/edmonds-karp/README.md new file mode 100644 index 00000000..aba114d4 --- /dev/null +++ b/src/algorithms/graph/flow-networks/edmonds-karp/README.md @@ -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|2)* time. + +Searching for an augmenting path P in the residual graph *Gf* 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) diff --git a/src/algorithms/graph/flow-networks/edmonds-karp/__test__/edmondsKarp.test.js b/src/algorithms/graph/flow-networks/edmonds-karp/__test__/edmondsKarp.test.js new file mode 100644 index 00000000..be9c2ce6 --- /dev/null +++ b/src/algorithms/graph/flow-networks/edmonds-karp/__test__/edmondsKarp.test.js @@ -0,0 +1,52 @@ +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 problem for simple graph', () => { + + 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)); + }); +}); diff --git a/src/algorithms/graph/flow-networks/edmonds-karp/edmondsKarp.js b/src/algorithms/graph/flow-networks/edmonds-karp/edmondsKarp.js new file mode 100644 index 00000000..e4cb6a37 --- /dev/null +++ b/src/algorithms/graph/flow-networks/edmonds-karp/edmondsKarp.js @@ -0,0 +1,197 @@ +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 {number} + * + * NB: capacities is a mapping from: GraphEdge.getKey() to number + * + */ +export default function edmondsKarp(graph,source,sink,capacities) { + + // variable to hold the max flow + // f gets incremented in 'augment(path)' + var f = 0; + + // set capacities where not defined to be infinity + graph.getAllEdges().forEach((edge) => { + if (!(edge.getKey() in capacities)) capacities[edge.getKey()] = Number.POSITIVE_INFINITY; + }); + + // the network flow is defined as the weights of graph + // assert the edges have 0 weight initally so that flow can be set to 0 + graph.getAllEdges().forEach((edge) => { + if (edge.weight != 0) throw "ASSERT: the edges of Graph 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 + let getFlow = (edge) => edge.weight; + let updateFlow = (edge,value) => {edge.weight+=value}; + + // 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 graph + for (let key in graph.getVerticesIndices()) residualGraph.addVertex(new GraphVertex(key)); + + // compute residual capacities and assign as weights to residualGraph + graph.getAllEdges().forEach((edge) => { + // edge is an edge in graph + // get edgeKey for extracting edge capacity from capacities + let edgeKey = edge.getKey(); + + // get the residualGraph start and end vertices (from edge in graph) + let startVertex = residualGraph.getVertexByKey(edge.startVertex.getKey()); + let endVertex = residualGraph.getVertexByKey(edge.endVertex.getKey()); + + // if the flow is less than the capacity: + if (getFlow(edge) 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(edge)>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(edge))); + } + }); + } + + /** + * 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; + for (let edge of edgePath) if (getFlow(edge) < bottleneck) bottleneck = getFlow(edge); + + for (let edgeResidualGraph of edgePath) { + // get the vertices in Graph corresponding to the edge in residualGraph + let startVertex = graph.getVertexByKey(edgeResidualGraph.startVertex.getKey()); + let endVertex = graph.getVertexByKey(edgeResidualGraph.endVertex.getKey()); + + // if it contains a forward edge, update the flow by adding bottleneck + let forwardEdge = graph.findEdge(startVertex,endVertex); + if (forwardEdge!=null) updateFlow(forwardEdge, bottleneck); + + // if it contains a backward edge, update the flow by adding -bottleneck + let backwardEdge = graph.findEdge(endVertex,startVertex); + if (backwardEdge!=null) updateFlow(backwardEdge, -bottleneck); + } + // increase the value of flow by bottleneck + f += bottleneck; + } + + // intialise the variable augmentingPath + var 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 + let s = source.getKey(); + let t = sink.getKey(); + // initialise a queue of vertices visited + let vertexPathQ = new Queue(); + vertexPathQ.enqueue([s]); + // initialise the currentPath array and seen object + let currentPath = []; + let seen = {}; + // initialise variable found – has sink been found + let found = false; + // Specify BFS traversal callbacks. + let bfsCallbacks = { + enterVertex: ({ currentVertex }) => { + // 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) + else if (nextVertex.getKey()!=t) seen[nextVertex.getKey()] = true; + // compute thisPath from currentPath and nextVertex + let 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 + let vertexPath = vertexPathQ.dequeue(); + // check that vertexPath exists and ends with the sink + if (vertexPath!=null && vertexPath[vertexPath.length-1]==t) { + // reset augmentingPath + augmentingPath = []; + + for (var i=0; if + +Construction of a residual graph Gf = ( V , Ef ) 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 cf(e) and edge set Ef that depend on the flow `f`. + +**Residual Graph Construction:** + +Start with empty Ef and cf(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 Ef with capacity *cf(e)* = `c(e) - f(e)` +- if `f(e) > 0`: add *backward* edge *e'=(v,u)* to Ef with capacity *cf(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 *Gf*. + +### 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) + From 6fae704a4e9ad3974daceb753c777995bab16fd9 Mon Sep 17 00:00:00 2001 From: Toby Date: Sat, 8 Apr 2023 18:54:00 +0100 Subject: [PATCH 2/3] small improvements on Edmonds Karp --- .../edmonds-karp/__test__/edmondsKarp.test.js | 4 +- .../flow-networks/edmonds-karp/edmondsKarp.js | 37 ++++++++++--------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/algorithms/graph/flow-networks/edmonds-karp/__test__/edmondsKarp.test.js b/src/algorithms/graph/flow-networks/edmonds-karp/__test__/edmondsKarp.test.js index be9c2ce6..8aa969d9 100644 --- a/src/algorithms/graph/flow-networks/edmonds-karp/__test__/edmondsKarp.test.js +++ b/src/algorithms/graph/flow-networks/edmonds-karp/__test__/edmondsKarp.test.js @@ -4,8 +4,7 @@ import Graph from '../../../../../data-structures/graph/Graph'; import edmondsKarp from '../edmondsKarp'; describe('edmondsKarp', () => { - it('should solve problem for simple graph', () => { - + 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'); @@ -47,6 +46,7 @@ describe('edmondsKarp', () => { 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)); }); }); diff --git a/src/algorithms/graph/flow-networks/edmonds-karp/edmondsKarp.js b/src/algorithms/graph/flow-networks/edmonds-karp/edmondsKarp.js index e4cb6a37..606d06b0 100644 --- a/src/algorithms/graph/flow-networks/edmonds-karp/edmondsKarp.js +++ b/src/algorithms/graph/flow-networks/edmonds-karp/edmondsKarp.js @@ -11,9 +11,10 @@ import Queue from '../../../../data-structures/queue/Queue'; * @param {GraphVertex} source * @param {GraphVertex} sink * @param {object} capacities - * @return {number} + * @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) { @@ -38,8 +39,8 @@ export default function edmondsKarp(graph,source,sink,capacities) { let residualGraph; // FLOW HELPER functions - let getFlow = (edge) => edge.weight; - let updateFlow = (edge,value) => {edge.weight+=value}; + const getFlow = (edge) => edge.weight; + const updateFlow = (edge,value) => {edge.weight+=value}; // FUNCTION for building Residual Graph // -> And hence determining residual capacities @@ -53,16 +54,16 @@ export default function edmondsKarp(graph,source,sink,capacities) { graph.getAllEdges().forEach((edge) => { // edge is an edge in graph // get edgeKey for extracting edge capacity from capacities - let edgeKey = edge.getKey(); + const edgeKey = edge.getKey(); // get the residualGraph start and end vertices (from edge in graph) - let startVertex = residualGraph.getVertexByKey(edge.startVertex.getKey()); - let endVertex = residualGraph.getVertexByKey(edge.endVertex.getKey()); + const startVertex = residualGraph.getVertexByKey(edge.startVertex.getKey()); + const endVertex = residualGraph.getVertexByKey(edge.endVertex.getKey()); // if the flow is less than the capacity: if (getFlow(edge) because (cap-flow) units of flow can still be pushed down the edge residualGraph.addEdge(new GraphEdge(startVertex,endVertex,capMinusFlow)); @@ -89,15 +90,15 @@ export default function edmondsKarp(graph,source,sink,capacities) { for (let edgeResidualGraph of edgePath) { // get the vertices in Graph corresponding to the edge in residualGraph - let startVertex = graph.getVertexByKey(edgeResidualGraph.startVertex.getKey()); - let endVertex = graph.getVertexByKey(edgeResidualGraph.endVertex.getKey()); + const startVertex = graph.getVertexByKey(edgeResidualGraph.startVertex.getKey()); + const endVertex = graph.getVertexByKey(edgeResidualGraph.endVertex.getKey()); // if it contains a forward edge, update the flow by adding bottleneck - let forwardEdge = graph.findEdge(startVertex,endVertex); + const forwardEdge = graph.findEdge(startVertex,endVertex); if (forwardEdge!=null) updateFlow(forwardEdge, bottleneck); // if it contains a backward edge, update the flow by adding -bottleneck - let backwardEdge = graph.findEdge(endVertex,startVertex); + const backwardEdge = graph.findEdge(endVertex,startVertex); if (backwardEdge!=null) updateFlow(backwardEdge, -bottleneck); } // increase the value of flow by bottleneck @@ -105,7 +106,7 @@ export default function edmondsKarp(graph,source,sink,capacities) { } // intialise the variable augmentingPath - var augmentingPath; + let augmentingPath; /** * findAugmentingPath: finds an augmenting path in residualGraph @@ -115,8 +116,8 @@ export default function edmondsKarp(graph,source,sink,capacities) { // intialise augmentingPath augmentingPath = []; // get the source and sink keys - let s = source.getKey(); - let t = sink.getKey(); + const s = source.getKey(); + const t = sink.getKey(); // initialise a queue of vertices visited let vertexPathQ = new Queue(); vertexPathQ.enqueue([s]); @@ -155,7 +156,7 @@ export default function edmondsKarp(graph,source,sink,capacities) { breadthFirstSearch(residualGraph,residualGraph.getVertexByKey(s),bfsCallbacks); // the search path is the next of vertexPathQ to dequeue - let vertexPath = vertexPathQ.dequeue(); + const vertexPath = vertexPathQ.dequeue(); // check that vertexPath exists and ends with the sink if (vertexPath!=null && vertexPath[vertexPath.length-1]==t) { // reset augmentingPath @@ -163,10 +164,10 @@ export default function edmondsKarp(graph,source,sink,capacities) { for (var i=0; i Date: Sat, 8 Apr 2023 19:47:39 +0100 Subject: [PATCH 3/3] fixed lint issues --- .../edmonds-karp/__test__/edmondsKarp.test.js | 16 ++- .../flow-networks/edmonds-karp/edmondsKarp.js | 115 ++++++++++-------- 2 files changed, 76 insertions(+), 55 deletions(-) diff --git a/src/algorithms/graph/flow-networks/edmonds-karp/__test__/edmondsKarp.test.js b/src/algorithms/graph/flow-networks/edmonds-karp/__test__/edmondsKarp.test.js index 8aa969d9..f776074d 100644 --- a/src/algorithms/graph/flow-networks/edmonds-karp/__test__/edmondsKarp.test.js +++ b/src/algorithms/graph/flow-networks/edmonds-karp/__test__/edmondsKarp.test.js @@ -5,7 +5,9 @@ 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 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'); @@ -37,15 +39,19 @@ describe('edmondsKarp', () => { .addEdge(edgeDT); // COMPUTE edmondsKarp - const edmondsKarpObj = edmondsKarp(graph,vertexS,vertexT,capacities); - + 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}) + 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)); }); diff --git a/src/algorithms/graph/flow-networks/edmonds-karp/edmondsKarp.js b/src/algorithms/graph/flow-networks/edmonds-karp/edmondsKarp.js index 606d06b0..9c9a3b25 100644 --- a/src/algorithms/graph/flow-networks/edmonds-karp/edmondsKarp.js +++ b/src/algorithms/graph/flow-networks/edmonds-karp/edmondsKarp.js @@ -17,21 +17,25 @@ import Queue from '../../../../data-structures/queue/Queue'; * & return is {graph:graph,maxflow:f} of types {Graph, Number} * */ -export default function edmondsKarp(graph,source,sink,capacities) { - +export default function edmondsKarp(graph, source, sink, capacities) { // variable to hold the max flow // f gets incremented in 'augment(path)' - var f = 0; + 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 - graph.getAllEdges().forEach((edge) => { - if (!(edge.getKey() in capacities)) capacities[edge.getKey()] = Number.POSITIVE_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 graph + // 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 - graph.getAllEdges().forEach((edge) => { - if (edge.weight != 0) throw "ASSERT: the edges of Graph should have 0 weight – the edges represent the flow – FordFulkerson asserts 0 inital flow in network"; + 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 @@ -39,42 +43,48 @@ export default function edmondsKarp(graph,source,sink,capacities) { let residualGraph; // FLOW HELPER functions - const getFlow = (edge) => edge.weight; - const updateFlow = (edge,value) => {edge.weight+=value}; + 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(){ + function buildResidualGraph() { // the weights of the residualGraph are the residual capacities residualGraph = new Graph(true); - // add to the residualGraph all the vertices in graph - for (let key in graph.getVerticesIndices()) residualGraph.addVertex(new GraphVertex(key)); + // 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 - graph.getAllEdges().forEach((edge) => { - // edge is an edge in graph + 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 graph) + // 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(edge) because (cap-flow) units of flow can still be pushed down the edge - residualGraph.addEdge(new GraphEdge(startVertex,endVertex,capMinusFlow)); + residualGraph.addEdge(new GraphEdge(startVertex, endVertex, capMinusFlow)); } // if the flow is non zero - if (getFlow(edge)>0) { + 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(edge))); + residualGraph.addEdge(new GraphEdge(endVertex, startVertex, getFlow(edgeKey))); } }); + // return residualGraph; } /** @@ -83,24 +93,29 @@ export default function edmondsKarp(graph,source,sink,capacities) { * * Considers the path in ResidualCap */ - function augment(edgePath){ + function augment(edgePath) { // compute the bottleneck capacity of the path in residualGraph let bottleneck = Number.POSITIVE_INFINITY; - for (let edge of edgePath) if (getFlow(edge) < bottleneck) bottleneck = getFlow(edge); + 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; + }); - for (let edgeResidualGraph of edgePath) { - // get the vertices in Graph corresponding to the edge in residualGraph - const startVertex = graph.getVertexByKey(edgeResidualGraph.startVertex.getKey()); - const endVertex = graph.getVertexByKey(edgeResidualGraph.endVertex.getKey()); + 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 = graph.findEdge(startVertex,endVertex); - if (forwardEdge!=null) updateFlow(forwardEdge, 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 = graph.findEdge(endVertex,startVertex); - if (backwardEdge!=null) updateFlow(backwardEdge, -bottleneck); - } + const backwardEdge = g.findEdge(endVertex, startVertex); + if (backwardEdge !== null) updateFlow(backwardEdge.getKey(), -bottleneck); + }); // increase the value of flow by bottleneck f += bottleneck; } @@ -112,23 +127,23 @@ export default function edmondsKarp(graph,source,sink,capacities) { * findAugmentingPath: finds an augmenting path in residualGraph * Uses BFS to determine the path (Edmonds-Karp specification) */ - function findAugmentingPath(){ + 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 - let vertexPathQ = new Queue(); + const vertexPathQ = new Queue(); vertexPathQ.enqueue([s]); // initialise the currentPath array and seen object let currentPath = []; - let seen = {}; + const seen = {}; // initialise variable found – has sink been found let found = false; // Specify BFS traversal callbacks. - let bfsCallbacks = { - enterVertex: ({ currentVertex }) => { + const bfsCallbacks = { + enterVertex: () => { // on entering vertex dequeue from vertex queue currentPath = vertexPathQ.dequeue(); }, @@ -138,36 +153,36 @@ export default function edmondsKarp(graph,source,sink,capacities) { // 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) - else if (nextVertex.getKey()!=t) seen[nextVertex.getKey()] = true; + if (nextVertex.getKey() !== t) seen[nextVertex.getKey()] = true; // compute thisPath from currentPath and nextVertex - let thisPath = []; - currentPath.forEach(v => thisPath.push(v)); + 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; + 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); + 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) { + if (vertexPath !== null && vertexPath[vertexPath.length - 1] === t) { // reset augmentingPath augmentingPath = []; - for (var i=0; i