diff --git a/graph/edmonds_karp.ts b/graph/edmonds_karp.ts new file mode 100644 index 00000000..fb781a42 --- /dev/null +++ b/graph/edmonds_karp.ts @@ -0,0 +1,97 @@ +import { StackQueue } from '../data_structures/queue/stack_queue' + +/** + * @function edmondsKarp + * @description Compute the maximum flow from a source node to a sink node using the Edmonds-Karp algorithm. + * @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(E) due to residual graph representation. + * @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 + */ +export default function edmondsKarp( + graph: [number, number][][], + source: number, + sink: number +): number { + const n = graph.length + + // Initialize residual graph + const residualGraph: [number, number][][] = Array.from( + { length: n }, + () => [] + ) + + // Build residual graph from the original graph + for (let u = 0; u < n; u++) { + for (const [v, cap] of graph[u]) { + if (cap > 0) { + residualGraph[u].push([v, cap]) // Forward edge + residualGraph[v].push([u, 0]) // Reverse edge with 0 capacity + } + } + } + + const findAugmentingPath = (parent: (number | null)[]): number => { + const visited = Array(n).fill(false) + const queue = new StackQueue() + queue.enqueue(source) + visited[source] = true + parent[source] = null + + while (queue.length() > 0) { + const u = queue.dequeue() + for (const [v, cap] of residualGraph[u]) { + if (!visited[v] && cap > 0) { + parent[v] = u + visited[v] = true + if (v === sink) { + // Return the bottleneck capacity along the path + let pathFlow = Infinity + let current = v + while (parent[current] !== null) { + const prev = parent[current]! + const edgeCap = residualGraph[prev].find( + ([node]) => node === current + )![1] + pathFlow = Math.min(pathFlow, edgeCap) + current = prev + } + return pathFlow + } + queue.enqueue(v) + } + } + } + return 0 + } + + let maxFlow = 0 + const parent = Array(n).fill(null) + + while (true) { + const pathFlow = findAugmentingPath(parent) + if (pathFlow === 0) break // No augmenting path found + + // Update the capacities and reverse capacities in the residual graph + let v = sink + while (parent[v] !== null) { + const u = parent[v]! + // Update capacity of the forward edge + const forwardEdge = residualGraph[u].find(([node]) => node === v)! + forwardEdge[1] -= pathFlow + // Update capacity of the reverse edge + const reverseEdge = residualGraph[v].find(([node]) => node === u)! + reverseEdge[1] += pathFlow + + v = u + } + + maxFlow += pathFlow + } + + return maxFlow +} diff --git a/graph/test/edmonds_karp.test.ts b/graph/test/edmonds_karp.test.ts new file mode 100644 index 00000000..22711ab9 --- /dev/null +++ b/graph/test/edmonds_karp.test.ts @@ -0,0 +1,82 @@ +import edmondsKarp from '../edmonds_karp' + +describe('Edmonds-Karp Algorithm', () => { + it('should find the maximum flow in a simple graph', () => { + const graph: [number, number][][] = [ + [ + [1, 3], + [2, 2] + ], // Node 0: Edges to node 1 (capacity 3), and node 2 (capacity 2) + [[3, 2]], // Node 1: Edge to node 3 (capacity 2) + [[3, 3]], // Node 2: Edge to node 3 (capacity 3) + [] // Node 3: No outgoing edges + ] + const source = 0 + const sink = 3 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(4) + }) + + it('should find the maximum flow in a more complex graph', () => { + const graph: [number, number][][] = [ + [ + [1, 10], + [2, 10] + ], // Node 0: Edges to node 1 and node 2 (both capacity 10) + [ + [3, 4], + [4, 8] + ], // Node 1: Edges to node 3 (capacity 4), and node 4 (capacity 8) + [[4, 9]], // Node 2: Edge to node 4 (capacity 9) + [[5, 10]], // Node 3: Edge to node 5 (capacity 10) + [[5, 10]], // Node 4: Edge to node 5 (capacity 10) + [] // Node 5: No outgoing edges (sink) + ] + const source = 0 + const sink = 5 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(14) + }) + + it('should return 0 when there is no path from source to sink', () => { + const graph: [number, number][][] = [ + [], // Node 0: No outgoing edges + [], // Node 1: No outgoing edges + [] // Node 2: No outgoing edges (sink) + ] + const source = 0 + const sink = 2 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(0) + }) + + it('should handle graphs with no edges', () => { + const graph: [number, number][][] = [ + [], // Node 0: No outgoing edges + [], // Node 1: No outgoing edges + [] // Node 2: No outgoing edges + ] + const source = 0 + const sink = 2 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(0) + }) + + it('should handle graphs with self-loops', () => { + const graph: [number, number][][] = [ + [ + [0, 10], + [1, 10] + ], // Node 0: Self-loop with capacity 10, and edge to node 1 (capacity 10) + [ + [1, 10], + [2, 10] + ], // Node 1: Self-loop and edge to node 2 + [] // Node 2: No outgoing edges (sink) + ] + const source = 0 + const sink = 2 + const maxFlow = edmondsKarp(graph, source, sink) + expect(maxFlow).toBe(10) + }) +}) diff --git a/search/binary_search.ts b/search/binary_search.ts index 39d380a4..cf86f39a 100644 --- a/search/binary_search.ts +++ b/search/binary_search.ts @@ -8,25 +8,25 @@ * * @param {number[]} array - sorted list of numbers * @param {number} target - target number to search for - * @return {number} - index of the target number in the list, or -1 if not found + * @return {number} - index of the target number in the list, or null if not found * @see [BinarySearch](https://www.geeksforgeeks.org/binary-search/) * @example binarySearch([1,2,3], 2) => 1 - * @example binarySearch([4,5,6], 2) => -1 + * @example binarySearch([4,5,6], 2) => null */ export const binarySearchIterative = ( array: number[], - target: number -): number => { - if (array.length === 0) return -1 - - // declare pointers for the start, middle and end indices - let start = 0, - end = array.length - 1, - middle = (start + end) >> 1 + target: number, + start: number = 0, + end: number = array.length - 1 +): number | null => { + if (array.length === 0) return null // ensure the target is within the bounds of the array - if (target < array[start] || target > array[end]) return -1 + if (target < array[start] || target > array[end]) return null + + // declare pointers for the middle index + let middle = (start + end) >> 1 while (array[middle] !== target && start <= end) { // if the target is less than the middle value, move the end pointer to be middle -1 to narrow the search space @@ -37,7 +37,7 @@ export const binarySearchIterative = ( middle = (start + end) >> 1 } // return the middle index if it is equal to target - return array[middle] === target ? middle : -1 + return array[middle] === target ? middle : null } export const binarySearchRecursive = ( @@ -45,16 +45,16 @@ export const binarySearchRecursive = ( target: number, start = 0, end = array.length - 1 -): number => { - if (array.length === 0) return -1 +): number | null => { + if (array.length === 0) return null // ensure the target is within the bounds of the array - if (target < array[start] || target > array[end]) return -1 + if (target < array[start] || target > array[end]) return null const middle = (start + end) >> 1 if (array[middle] === target) return middle // target found - if (start > end) return -1 // target not found + if (start > end) return null // target not found // if the target is less than the middle value, move the end pointer to be middle -1 to narrow the search space // otherwise, move the start pointer to be middle + 1 diff --git a/search/exponential_search.ts b/search/exponential_search.ts new file mode 100644 index 00000000..4a8eba47 --- /dev/null +++ b/search/exponential_search.ts @@ -0,0 +1,40 @@ +import { binarySearchIterative } from './binary_search' + +/** + * @description Exponential search algorithm for a sorted array. + * + * The algorithm searches for a specific value in a sorted array by first finding a range + * where the value may be present and then performing a binary search within that range. + * + * Compared with binary search, exponential search can be more convenient and advantageous + * in cases where the element to be searched is closer to the beginning of the array, + * thus avoiding several comparisons that would make the search more verbose. + * + * @param {number[]} array - sorted list of numbers + * @param {number} x - target number to search for + * @return {number | null} - index of the target number in the list, or null if not found + * @see [ExponentialSearch](https://www.geeksforgeeks.org/exponential-search/) + * @example exponentialSearch([1, 2, 3, 4, 5], 3) => 2 + * @example exponentialSearch([10, 20, 30, 40, 50], 35) => null + */ + +export const exponentialSearch = ( + array: number[], + x: number +): number | null => { + const arrayLength = array.length + if (arrayLength === 0) return null + + if (array[0] === x) return 0 + + let i = 1 + while (i < arrayLength && array[i] <= x) { + i = i * 2 + } + + const start = Math.floor(i / 2) + const end = Math.min(i, arrayLength - 1) + const result = binarySearchIterative(array, x, start, end) + + return result +} diff --git a/search/fibonacci_search.ts b/search/fibonacci_search.ts new file mode 100644 index 00000000..b0125277 --- /dev/null +++ b/search/fibonacci_search.ts @@ -0,0 +1,57 @@ +/** + * @description Fibonacci search algorithm for a sorted array. + * + * The algorithm searches for a specific value in a sorted array using Fibonacci numbers + * to divide the array into smaller subarrays. This algorithm is useful for large arrays where + * the cost of accessing elements is high. + * + * @param {number[]} array - sorted list of numbers + * @param {number} target - target number to search for + * @return {number | null} - index of the target number in the list, or null if not found + * @see [FibonacciSearch](https://www.geeksforgeeks.org/fibonacci-search/) + * @example fibonacciSearch([1,2,3], 2) => 1 + * @example fibonacciSearch([4,5,6], 2) => null + */ + +export const fibonacciSearch = ( + array: number[], + target: number +): number | null => { + const arrayLength = array.length + let a = 0 // (n-2)'th Fibonacci No. + let b = 1 // (n-1)'th Fibonacci No. + let c = a + b // n'th Fibonacci + + while (c < arrayLength) { + a = b + b = c + c = a + b + } + + let offset = -1 + + while (c > 1) { + let i = Math.min(offset + a, arrayLength - 1) + + if (array[i] < target) { + c = b + b = a + a = c - b + offset = i + } else if (array[i] > target) { + c = a + b = b - a + a = c - b + } else { + // Element found then return index + return i + } + } + + if (b && array[offset + 1] === target) { + return offset + 1 + } + + // Element not found then return null + return null +} diff --git a/search/test/binary_search.test.ts b/search/test/binary_search.test.ts index 6ebd12d9..13b13251 100644 --- a/search/test/binary_search.test.ts +++ b/search/test/binary_search.test.ts @@ -2,7 +2,7 @@ import { binarySearchIterative, binarySearchRecursive } from '../binary_search' describe('BinarySearch', () => { const testArray: number[] = [1, 2, 3, 4] - type FunctionsArray = { (array: number[], index: number): number }[] + type FunctionsArray = { (array: number[], index: number): number | null }[] const functions: FunctionsArray = [ binarySearchIterative, binarySearchRecursive @@ -12,14 +12,16 @@ describe('BinarySearch', () => { it('should be defined', () => { expect(func(testArray, 2)).toBeDefined() }) - it('should return a number', () => { - expect(typeof func(testArray, 2)).toBe('number') + it('should return a number or null', () => { + expect( + typeof func(testArray, 2) === 'number' || func(testArray, 2) === null + ).toBe(true) }) - it('should return -1 if the target is not found in the array', () => { - expect(func(testArray, 5)).toBe(-1) + it('should return null if the target is not found in the array', () => { + expect(func(testArray, 5)).toBe(null) }) - it('should return -1 if there are no elements in the array', () => { - expect(func([], 5)).toBe(-1) + it('should return null if there are no elements in the array', () => { + expect(func([], 5)).toBe(null) }) it('should return the index of the target if it is found in the array', () => { expect(func(testArray, 2)).toBe(1) diff --git a/search/test/exponential_search.test.ts b/search/test/exponential_search.test.ts new file mode 100644 index 00000000..80f6a07f --- /dev/null +++ b/search/test/exponential_search.test.ts @@ -0,0 +1,19 @@ +import { exponentialSearch } from '../exponential_search' + +describe('Exponential search', () => { + test.each([ + [[1, 2, 3, 4, 5], 3, 2], + [[10, 20, 30, 40, 50], 35, null], + [[10, 20, 30, 40, 50], 10, 0], + [[10, 20, 30, 40, 50], 50, 4], + [[10, 20, 30, 40, 50], 60, null], + [[], 10, null], + [[1, 2, 3, 4, 5], 1, 0], + [[1, 2, 3, 4, 5], 5, 4] + ])( + 'of %o, searching for %o, expected %i', + (array: number[], target: number, expected: number | null) => { + expect(exponentialSearch(array, target)).toBe(expected) + } + ) +}) diff --git a/search/test/fibonacci_search.test.ts b/search/test/fibonacci_search.test.ts new file mode 100644 index 00000000..5b2b54b7 --- /dev/null +++ b/search/test/fibonacci_search.test.ts @@ -0,0 +1,18 @@ +import { fibonacciSearch } from '../fibonacci_search' + +describe('Fibonacci search', () => { + test.each([ + [[1, 2, 3], 2, 1], + [[4, 5, 6], 2, null], + [[10, 22, 35, 40, 45, 50, 80, 82, 85, 90, 100], 85, 8], + [[], 1, null], + [[1], 1, 0], + [[1, 3, 5, 7, 9, 11, 13], 11, 5], + [[1, 3, 5, 7, 9, 11, 13], 8, null] + ])( + 'of %o, searching for %o, expected %i', + (array: number[], target: number, expected: number | null) => { + expect(fibonacciSearch(array, target)).toBe(expected) + } + ) +})