Skip to content
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

Added the implementation of the Edmond Karp algorithm along with test cases #252

Merged
merged 12 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions graph/edmondkarp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* @function edmondkarp
mapcrafter2048 marked this conversation as resolved.
Show resolved Hide resolved
* @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 3-tuple where the 0'th item is the destination node, the 1'th item is the edge weight, and the 2'nd item is the edge capacity.
mapcrafter2048 marked this conversation as resolved.
Show resolved Hide resolved
* @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) where V is the number of vertices
mapcrafter2048 marked this conversation as resolved.
Show resolved Hide resolved
* @param {[number, 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, number][][], source: number, sink: number): number {
// Initialize capacity and flow matrices with zeros and build capacity matrix from graph
const n = graph.length;
const capacity = Array.from({ length: n }, () => Array(n).fill(0));
const flow = Array.from({ length: n }, () => Array(n).fill(0));

// Build capacity matrix
for (let u = 0; u < n; u++) {
for (const [v, , cap] of graph[u]) {
mapcrafter2048 marked this conversation as resolved.
Show resolved Hide resolved
capacity[u][v] = cap;
}
}

// Breadth-first search
const bfs = (parent: number[]): boolean => {
const visited = Array(n).fill(false);
const queue: number[] = [];
queue.push(source);
visited[source] = true;

// Find an augmenting path from source to sink by doing a BFS traversal
while (queue.length > 0) {
// Dequeue
const u = queue.shift()!;
appgurueu marked this conversation as resolved.
Show resolved Hide resolved
// Enqueue all adjacent unvisited vertices with available capacity
for (let v = 0; v < n; v++) {
mapcrafter2048 marked this conversation as resolved.
Show resolved Hide resolved
// If there is available capacity and the vertex has not been visited
if (!visited[v] && capacity[u][v] - flow[u][v] > 0) {
queue.push(v);
visited[v] = true;
parent[v] = u;
// If we reach the sink, we have found the augmenting path
mapcrafter2048 marked this conversation as resolved.
Show resolved Hide resolved
if (v === sink) {
return true;
}
}
}
}
return false;
};

let maxFlow = 0;
const parent = Array(n).fill(-1);
mapcrafter2048 marked this conversation as resolved.
Show resolved Hide resolved

while (bfs(parent)) {
let pathFlow = Infinity;
// Find the maximum flow through the path found
for (let v = sink; v !== source; v = parent[v]) {
const u = parent[v];
pathFlow = Math.min(pathFlow, capacity[u][v] - flow[u][v]);
}
// Update the flow matrix
for (let v = sink; v !== source; v = parent[v]) {
const u = parent[v];
flow[u][v] += pathFlow;
flow[v][u] -= pathFlow;
}

maxFlow += pathFlow;
}

return maxFlow;
}
84 changes: 84 additions & 0 deletions graph/test/edmondkarp_test.ts
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);
}
);
});
27 changes: 27 additions & 0 deletions maths/bisection_method.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please get rid of these unrelated changes in this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid this isn't resolved. The changes are still there.

Copy link
Contributor

@appgurueu appgurueu Oct 10, 2024

Choose a reason for hiding this comment

The 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 (bisection_method.ts, decimal_convert.ts, euler_method.ts)?

* @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
}
20 changes: 20 additions & 0 deletions maths/decimal_convert.ts
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
}
25 changes: 25 additions & 0 deletions maths/euler_method.ts
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
}
Loading