mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-11-13 06:23:00 +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