mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-11-10 11:09:43 +08:00
Merge b78b003382
into ca3d16dcce
This commit is contained in:
commit
d1f599332d
19
src/algorithms/graph/flow-networks/edmonds-karp/README.md
Normal file
19
src/algorithms/graph/flow-networks/edmonds-karp/README.md
Normal 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)
|
@ -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));
|
||||
});
|
||||
});
|
213
src/algorithms/graph/flow-networks/edmonds-karp/edmondsKarp.js
Normal file
213
src/algorithms/graph/flow-networks/edmonds-karp/edmondsKarp.js
Normal 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 };
|
||||
}
|
67
src/algorithms/graph/flow-networks/ford-fulkerson/README.md
Normal file
67
src/algorithms/graph/flow-networks/ford-fulkerson/README.md
Normal file
@ -0,0 +1,67 @@
|
||||
# Ford-Fulkerson
|
||||
|
||||
The Ford–Fulkerson 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)
|
||||
|
Loading…
Reference in New Issue
Block a user