-
-
Notifications
You must be signed in to change notification settings - Fork 411
Added the implementation of the Edmond Karp algorithm along with test cases #252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
9b477f1
a358ea6
b560c2e
13ba5a3
ab770c3
6fed73d
b5daf9f
4b5f2b5
76dd412
2aa4555
f97e200
97adbc8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/** | ||
* @description Compute the maximum flow from a source node to a sink node. The input graph is in adjacency list form. It is a multidimensional array of edges. graph[i] holds the edges for the i'th node. Each edge is a 2-tuple where the 0'th item is the destination node, and the 1'st item is the edge capacity. | ||
* @Complexity_Analysis | ||
* Time complexity: O(V * E^2) where V is the number of vertices and E is the number of edges | ||
* Space Complexity: O(V^2) where V is the number of vertices | ||
* @param {[number, number][][]} graph - The graph in adjacency list form | ||
* @param {number} source - The source node | ||
* @param {number} sink - The sink node | ||
* @return {number} - The maximum flow from the source node to the sink node | ||
* @see https://en.wikipedia.org/wiki/Edmonds%E2%80%93Karp_algorithm | ||
*/ | ||
|
||
function edmondkarp(graph: [number, number][][], source: number, sink: number): number { | ||
const n = graph.length; | ||
|
||
// Residual graph in adjacency list form | ||
const residualGraph: Map<number, [number, number][]> = new Map(); | ||
mapcrafter2048 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for (let u = 0; u < n; u++) { | ||
residualGraph.set(u, []); | ||
for (const [v, capacity] of graph[u]) { | ||
residualGraph.get(u)?.push([v, capacity]); | ||
if (!residualGraph.has(v)) { | ||
residualGraph.set(v, []); | ||
} | ||
} | ||
} | ||
|
||
const parent = Array(n).fill(null); | ||
let maxFlow = 0; | ||
|
||
// Level-order BFS using two level arrays | ||
const bfs = (): boolean => { | ||
mapcrafter2048 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const visited = Array(n).fill(false); | ||
const currentLevel: number[] = []; | ||
const nextLevel: number[] = []; | ||
currentLevel.push(source); | ||
visited[source] = true; | ||
|
||
while (currentLevel.length > 0) { | ||
nextLevel.length = 0; | ||
|
||
for (const u of currentLevel) { | ||
for (const [v, capacity] of residualGraph.get(u)!) { | ||
if (!visited[v] && capacity > 0) { | ||
parent[v] = u; | ||
visited[v] = true; | ||
|
||
// If we reach the sink, we have found an augmenting path | ||
if (v === sink) { | ||
return true; | ||
} | ||
|
||
nextLevel.push(v); | ||
} | ||
} | ||
} | ||
|
||
currentLevel.length = 0; | ||
currentLevel.push(...nextLevel); | ||
} | ||
|
||
return false; | ||
}; | ||
|
||
while (bfs()) { | ||
let pathFlow = Infinity; | ||
|
||
// Find the maximum flow through the path found | ||
for (let v = sink; v !== source; v = parent[v]!) { | ||
const u = parent[v]!; | ||
const edge = residualGraph.get(u)!.find(([dest]) => dest === v)!; | ||
pathFlow = Math.min(pathFlow, edge[1]); | ||
} | ||
|
||
// Update the residual graph | ||
for (let v = sink; v !== source; v = parent[v]!) { | ||
const u = parent[v]!; | ||
|
||
// Update forward edge | ||
const edgeIndex = residualGraph.get(u)!.findIndex(([dest]) => dest === v); | ||
residualGraph.get(u)![edgeIndex][1] -= pathFlow; | ||
|
||
// Update backward edge | ||
const reverseEdge = residualGraph.get(v)!.find(([dest]) => dest === u); | ||
if (reverseEdge) { | ||
reverseEdge[1] += pathFlow; | ||
} else { | ||
residualGraph.get(v)!.push([u, pathFlow]); | ||
} | ||
} | ||
|
||
maxFlow += pathFlow; | ||
} | ||
|
||
return maxFlow; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { edmondsKarp } from '../edmondsKarp' | ||
|
||
describe('edmondsKarp', () => { | ||
const init_flow_network = (N: number): number[][] => { | ||
const graph = Array.from({ length: N }, () => Array(N).fill(0)); | ||
return graph; | ||
} | ||
|
||
const add_capacity = ( | ||
graph: number[][], | ||
u: number, | ||
v: number, | ||
capacity: number | ||
) => { | ||
graph[u][v] = capacity; | ||
} | ||
|
||
it('should return the correct maximum flow value for basic graph', () => { | ||
const graph = init_flow_network(6); | ||
add_capacity(graph, 0, 1, 16); | ||
add_capacity(graph, 0, 2, 13); | ||
add_capacity(graph, 1, 2, 10); | ||
add_capacity(graph, 1, 3, 12); | ||
add_capacity(graph, 2, 1, 4); | ||
add_capacity(graph, 2, 4, 14); | ||
add_capacity(graph, 3, 2, 9); | ||
add_capacity(graph, 3, 5, 20); | ||
add_capacity(graph, 4, 3, 7); | ||
add_capacity(graph, 4, 5, 4); | ||
expect(edmondsKarp(graph, 0, 5)).toBe(23); | ||
}); | ||
|
||
it('should return the correct maximum flow value for single element graph', () => { | ||
const graph = init_flow_network(1); | ||
expect(edmondsKarp(graph, 0, 0)).toBe(0); | ||
}); | ||
|
||
const linear_flow_network = init_flow_network(4); | ||
add_capacity(linear_flow_network, 0, 1, 10); | ||
add_capacity(linear_flow_network, 1, 2, 5); | ||
add_capacity(linear_flow_network, 2, 3, 15); | ||
test.each([ | ||
[0, 3, 5], | ||
[0, 2, 5], | ||
[1, 3, 5], | ||
[1, 2, 5], | ||
])( | ||
'correct result for linear flow network with source node %i and sink node %i', | ||
(source, sink, maxFlow) => { | ||
expect(edmondsKarp(linear_flow_network, source, sink)).toBe(maxFlow); | ||
} | ||
); | ||
|
||
const disconnected_flow_network = init_flow_network(4); | ||
add_capacity(disconnected_flow_network, 0, 1, 10); | ||
add_capacity(disconnected_flow_network, 2, 3, 5); | ||
test.each([ | ||
[0, 3, 0], | ||
[1, 2, 0], | ||
[2, 3, 5], | ||
])( | ||
'correct result for disconnected flow network with source node %i and sink node %i', | ||
(source, sink, maxFlow) => { | ||
expect(edmondsKarp(disconnected_flow_network, source, sink)).toBe(maxFlow); | ||
} | ||
); | ||
|
||
const cyclic_flow_network = init_flow_network(5); | ||
add_capacity(cyclic_flow_network, 0, 1, 10); | ||
add_capacity(cyclic_flow_network, 1, 2, 5); | ||
add_capacity(cyclic_flow_network, 2, 0, 7); | ||
add_capacity(cyclic_flow_network, 2, 3, 10); | ||
add_capacity(cyclic_flow_network, 3, 4, 10); | ||
test.each([ | ||
[0, 4, 10], | ||
[1, 4, 10], | ||
[2, 4, 10], | ||
])( | ||
'correct result for cyclic flow network with source node %i and sink node %i', | ||
(source, sink, maxFlow) => { | ||
expect(edmondsKarp(cyclic_flow_network, source, sink)).toBe(maxFlow); | ||
} | ||
); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/** | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please get rid of these unrelated changes in this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm afraid this isn't resolved. The changes are still there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still there. Maybe you forgot to push a commit that gets rid of these additions ( |
||
* @function bisectionMethod | ||
* @description Bisection method is a root-finding method that applies to any continuous function for which one knows two values with opposite signs. | ||
* @param {number} a - The first value | ||
* @param {number} b - The second value | ||
* @param {number} e - The error value | ||
* @param {Function} f - The function | ||
* @return {number} - The root of the function | ||
* @see [BisectionMethod](https://en.wikipedia.org/wiki/Bisection_method) | ||
* @example bisectionMethod(1, 2, 0.01, (x) => x**2 - 2) = 1.4140625 | ||
* @example bisectionMethod(1, 2, 0.01, (x) => x**2 - 3) = 1.732421875 | ||
*/ | ||
|
||
export const bisectionMethod = (a: number, b: number, e: number, f: Function): number => { | ||
let c = a | ||
while ((b - a) >= e) { | ||
c = (a + b) / 2 | ||
if (f(c) === 0.0) { | ||
break | ||
} else if (f(c) * f(a) < 0) { | ||
b = c | ||
} else { | ||
a = c | ||
} | ||
} | ||
return c | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/** | ||
* @function decimalConvert | ||
* @description Convert the binary to decimal. | ||
* @param {string} binary - The input binary | ||
* @return {number} - Decimal of binary. | ||
* @see [DecimalConvert](https://www.programiz.com/javascript/examples/binary-decimal) | ||
* @example decimalConvert(1100) = 12 | ||
* @example decimalConvert(1110) = 14 | ||
*/ | ||
|
||
export const decimalConvert = (binary: string): number => { | ||
let decimal = 0 | ||
let binaryArr = binary.split('').reverse() | ||
|
||
for (let i = 0; i < binaryArr.length; i++) { | ||
decimal += parseInt(binaryArr[i]) * Math.pow(2, i) | ||
} | ||
|
||
return decimal | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/** | ||
* @function eulerMethod | ||
* @description Euler's method is a first-order numerical procedure for solving ordinary differential equations (ODEs) with a given initial value. | ||
* @param {number} x0 - The initial value of x | ||
* @param {number} y0 - The initial value of y | ||
* @param {number} h - The step size | ||
* @param {number} n - The number of iterations | ||
* @param {Function} f - The function | ||
* @return {number} - The value of y at x | ||
* @see [EulerMethod](https://en.wikipedia.org/wiki/Euler_method) | ||
* @example eulerMethod(0, 1, 0.1, 10, (x, y) => x + y) = 2.5937424601 | ||
* @example eulerMethod(0, 1, 0.1, 10, (x, y) => x * y) = 1.7715614317 | ||
*/ | ||
|
||
export const eulerMethod = (x0: number, y0: number, h: number, n: number, f: Function): number => { | ||
let x = x0 | ||
let y = y0 | ||
|
||
for (let i = 1; i <= n; i++) { | ||
y = y + h * f(x, y) | ||
x = x + h | ||
} | ||
|
||
return y | ||
} |
Uh oh!
There was an error while loading. Please reload this page.