Advanced Algorithms
Level 3: Intermediate Advanced - Master sophisticated algorithms for complex problem solving
Advanced Algorithms
Advanced Graph Algorithms
DFS/BFS Variations & Topological Sort
Beyond basic traversal, DFS and BFS can be adapted for complex graph problems including cycle detection, topological sorting, and finding strongly connected components.
🗺️ Real-Life Analogy:
Think of topological sort as planning a course schedule: you must take prerequisites before advanced courses. DFS helps find the right order by exploring dependencies deeply.
🔍 Dijkstra's Algorithm Step-by-Step:
Graph: A→B(4), A→C(2), B→C(1), B→D(5), C→D(8), C→E(10), D→E(2) Source: A, Find shortest paths to all vertices Step 1: Initialize distances A B C D E 0 ∞ ∞ ∞ ∞ ← A is source (distance 0) Step 2: Process A (current = A, distance = 0) A B C D E 0 4 2 ∞ ∞ ← Update neighbors of A ↑ ↑ A→B(4) A→C(2) Step 3: Process C (current = C, distance = 2) A B C D E 0 3 2 10 12 ← Update via C: B=min(4,2+1)=3, D=2+8=10, E=2+10=12 ↑ ↑ ↑ C→B(1) C→D(8) C→E(10) Step 4: Process B (current = B, distance = 3) A B C D E 0 3 2 8 12 ← Update via B: D=min(10,3+5)=8 ↑ B→D(5) Step 5: Process D (current = D, distance = 8) A B C D E 0 3 2 8 10 ← Update via D: E=min(12,8+2)=10 ↑ D→E(2) Final shortest paths from A: A→A: 0, A→B: 3, A→C: 2, A→D: 8, A→E: 10 Paths: A, A→C→B, A→C, A→C→B→D, A→C→B→D→E
🌍 Real-World Applications:
🗺️ GPS Navigation: Finding shortest routes between locations considering traffic and road conditions
🌐 Network Routing: Determining optimal paths for data packets across internet infrastructure
🎮 Game AI: Pathfinding for NPCs and optimal movement in game worlds
📊 Social Networks: Finding shortest connection paths between users (degrees of separation)
🏭 Supply Chain: Optimizing logistics and delivery routes to minimize cost and time
🔍 Advanced Graph Applications:
- Topological Sort: Order tasks with dependencies
- Cycle Detection: Find circular dependencies
- Strongly Connected Components: Find groups with mutual reachability
- Bipartite Checking: Two-coloring problems
TypeScript
// Bellman-Ford Algorithm for shortest paths with negative weights class BellmanFord { findShortestPaths(edges: [number, number, number][], vertices: number, source: number): { distances: number[], hasNegativeCycle: boolean } { const distances = new Array(vertices).fill(Infinity); distances[source] = 0; // Relax edges V-1 times for (let i = 0; i < vertices - 1; i++) { for (const [u, v, weight] of edges) { if (distances[u] !== Infinity && distances[u] + weight < distances[v]) { distances[v] = distances[u] + weight; } } } // Check for negative cycles let hasNegativeCycle = false; for (const [u, v, weight] of edges) { if (distances[u] !== Infinity && distances[u] + weight < distances[v]) { hasNegativeCycle = true; break; } } return { distances, hasNegativeCycle }; } } // Floyd-Warshall Algorithm for all-pairs shortest paths class FloydWarshall { allPairsShortestPaths(matrix: number[][]): number[][] { const n = matrix.length; const dist = matrix.map(row => [...row]); // Initialize with infinity for no direct path for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { if (i !== j && dist[i][j] === 0) { dist[i][j] = Infinity; } } } // Try all intermediate vertices for (let k = 0; k < n; k++) { for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { if (dist[i][k] + dist[k][j] < dist[i][j]) { dist[i][j] = dist[i][k] + dist[k][j]; } } } } return dist; } } // Topological Sort using DFS class TopologicalSort { private visited: Set= new Set(); private stack: number[] = []; topologicalSort(graph: Map ): number[] { this.visited.clear(); this.stack = []; for (const vertex of graph.keys()) { if (!this.visited.has(vertex)) { this.dfs(vertex, graph); } } return this.stack.reverse(); } private dfs(vertex: number, graph: Map ): void { this.visited.add(vertex); const neighbors = graph.get(vertex) || []; for (const neighbor of neighbors) { if (!this.visited.has(neighbor)) { this.dfs(neighbor, graph); } } this.stack.push(vertex); } }
Python
# Bellman-Ford Algorithm class BellmanFord: def find_shortest_paths(self, edges, vertices, source): """Find shortest paths with negative edge detection""" distances = [float('inf')] * vertices distances[source] = 0 # Relax edges V-1 times for _ in range(vertices - 1): for u, v, weight in edges: if distances[u] != float('inf') and distances[u] + weight < distances[v]: distances[v] = distances[u] + weight # Check for negative cycles has_negative_cycle = False for u, v, weight in edges: if distances[u] != float('inf') and distances[u] + weight < distances[v]: has_negative_cycle = True break return distances, has_negative_cycle # Floyd-Warshall Algorithm class FloydWarshall: def all_pairs_shortest_paths(self, matrix): """Find shortest paths between all pairs of vertices""" n = len(matrix) dist = [row[:] for row in matrix] # Initialize with infinity for no direct path for i in range(n): for j in range(n): if i != j and dist[i][j] == 0: dist[i][j] = float('inf') # Try all intermediate vertices for k in range(n): for i in range(n): for j in range(n): if dist[i][k] + dist[k][j] < dist[i][j]: dist[i][j] = dist[i][k] + dist[k][j] return dist def has_negative_cycle(self, matrix): """Check if graph has negative cycle""" dist = self.all_pairs_shortest_paths(matrix) # Check diagonal for negative values for i in range(len(dist)): if dist[i][i] < 0: return True return False # Topological Sort class TopologicalSort: def __init__(self): self.visited = set() self.stack = [] def topological_sort(self, graph): """Perform topological sort using DFS""" self.visited.clear() self.stack = [] for vertex in graph: if vertex not in self.visited: self._dfs(vertex, graph) return self.stack[::-1] def _dfs(self, vertex, graph): """DFS helper for topological sort""" self.visited.add(vertex) for neighbor in graph.get(vertex, []): if neighbor not in self.visited: self._dfs(neighbor, graph) self.stack.append(vertex)
OCaml
(* Bellman-Ford Algorithm *) module BellmanFord = struct type edge = int * int * int (* u, v, weight *) let find_shortest_paths edges vertices source = let distances = Array.make vertices max_int in distances.(source) <- 0; (* Relax edges V-1 times *) for _ = 1 to vertices - 1 do List.iter (fun (u, v, weight) -> if distances.(u) <> max_int && distances.(u) + weight < distances.(v) then distances.(v) <- distances.(u) + weight ) edges done; (* Check for negative cycles *) let has_negative_cycle = ref false in List.iter (fun (u, v, weight) -> if distances.(u) <> max_int && distances.(u) + weight < distances.(v) then has_negative_cycle := true ) edges; (distances, !has_negative_cycle) end (* Floyd-Warshall Algorithm *) module FloydWarshall = struct let all_pairs_shortest_paths matrix = let n = Array.length matrix in let dist = Array.make_matrix n n 0 in (* Initialize distance matrix *) for i = 0 to n - 1 do for j = 0 to n - 1 do dist.(i).(j) <- if i = j then 0 else if matrix.(i).(j) = 0 then max_int else matrix.(i).(j) done done; (* Try all intermediate vertices *) for k = 0 to n - 1 do for i = 0 to n - 1 do for j = 0 to n - 1 do if dist.(i).(k) <> max_int && dist.(k).(j) <> max_int then let new_dist = dist.(i).(k) + dist.(k).(j) in if new_dist < dist.(i).(j) then dist.(i).(j) <- new_dist done done done; dist end (* Topological Sort *) module TopologicalSort = struct let topological_sort graph = let visited = Hashtbl.create 16 in let stack = ref [] in let rec dfs vertex = if not (Hashtbl.mem visited vertex) then begin Hashtbl.add visited vertex true; let neighbors = try Hashtbl.find graph vertex with Not_found -> [] in List.iter dfs neighbors; stack := vertex :: !stack end in Hashtbl.iter (fun vertex _ -> dfs vertex) graph; !stack end
Algorithm | Time Complexity | Space Complexity | Use Case |
---|---|---|---|
Bellman-Ford | O(VE) | O(V) | Negative weight edges |
Floyd-Warshall | O(V³) | O(V²) | All-pairs shortest paths |
Topological Sort | O(V + E) | O(V) | Task scheduling |
String Algorithms
Pattern Matching Algorithms
String algorithms optimize pattern searching beyond naive O(nm) approaches. KMP uses failure functions, while Rabin-Karp employs rolling hashes for efficient pattern matching.
🔍 Real-Life Analogy:
Think of finding a word in a book: instead of checking every position character by character, KMP remembers partial matches like knowing "comput" in "computer" means you can skip ahead when you see "com" again.
📝 String Algorithm Applications:
- Text Editors: Find and replace functionality
- DNA Sequencing: Pattern matching in genetic data
- Plagiarism Detection: Finding similar text segments
- Network Security: Intrusion detection patterns
TypeScript
// KMP (Knuth-Morris-Pratt) Algorithm class KMPMatcher { // Build failure function (longest proper prefix which is also suffix) private buildFailureFunction(pattern: string): number[] { const m = pattern.length; const failure = new Array(m).fill(0); let j = 0; for (let i = 1; i < m; i++) { while (j > 0 && pattern[i] !== pattern[j]) { j = failure[j - 1]; } if (pattern[i] === pattern[j]) { j++; } failure[i] = j; } return failure; } // Find all occurrences of pattern in text search(text: string, pattern: string): number[] { if (pattern.length === 0) return []; const n = text.length; const m = pattern.length; const failure = this.buildFailureFunction(pattern); const matches: number[] = []; let j = 0; for (let i = 0; i < n; i++) { while (j > 0 && text[i] !== pattern[j]) { j = failure[j - 1]; } if (text[i] === pattern[j]) { j++; } if (j === m) { matches.push(i - m + 1); j = failure[j - 1]; } } return matches; } } // Rabin-Karp Algorithm class RabinKarpMatcher { private readonly prime = 101; private readonly base = 256; search(text: string, pattern: string): number[] { const n = text.length; const m = pattern.length; const matches: number[] = []; if (m > n) return matches; // Calculate hash values let patternHash = 0; let textHash = 0; let h = 1; // Calculate h = base^(m-1) % prime for (let i = 0; i < m - 1; i++) { h = (h * this.base) % this.prime; } // Calculate initial hash values for (let i = 0; i < m; i++) { patternHash = (this.base * patternHash + pattern.charCodeAt(i)) % this.prime; textHash = (this.base * textHash + text.charCodeAt(i)) % this.prime; } // Slide the pattern over text for (let i = 0; i <= n - m; i++) { // Check if hash values match if (patternHash === textHash) { // Verify character by character let j; for (j = 0; j < m; j++) { if (text[i + j] !== pattern[j]) break; } if (j === m) matches.push(i); } // Calculate hash for next window if (i < n - m) { textHash = (this.base * (textHash - text.charCodeAt(i) * h) + text.charCodeAt(i + m)) % this.prime; // Handle negative hash value if (textHash < 0) textHash += this.prime; } } return matches; } } // Example: Find repeated DNA sequences function findRepeatedDnaSequences(s: string): string[] { const seen = new Set(); const repeated = new Set (); for (let i = 0; i <= s.length - 10; i++) { const sequence = s.substring(i, i + 10); if (seen.has(sequence)) { repeated.add(sequence); } seen.add(sequence); } return Array.from(repeated); }
Python
# KMP (Knuth-Morris-Pratt) Algorithm class KMP: def build_failure_function(self, pattern): """Build the failure function for KMP""" m = len(pattern) failure = [0] * m j = 0 for i in range(1, m): while j > 0 and pattern[i] != pattern[j]: j = failure[j - 1] if pattern[i] == pattern[j]: j += 1 failure[i] = j return failure def search(self, text, pattern): """Find all occurrences of pattern in text""" if not pattern: return [] n, m = len(text), len(pattern) failure = self.build_failure_function(pattern) matches = [] j = 0 for i in range(n): while j > 0 and text[i] != pattern[j]: j = failure[j - 1] if text[i] == pattern[j]: j += 1 if j == m: matches.append(i - m + 1) j = failure[j - 1] return matches # Rabin-Karp Algorithm class RabinKarp: def __init__(self, base=256, prime=101): self.base = base self.prime = prime def search(self, text, pattern): """Find all occurrences using rolling hash""" n, m = len(text), len(pattern) matches = [] if m > n: return matches # Calculate hash values pattern_hash = 0 text_hash = 0 h = 1 # h = base^(m-1) % prime for i in range(m - 1): h = (h * self.base) % self.prime # Calculate initial hash values for i in range(m): pattern_hash = (self.base * pattern_hash + ord(pattern[i])) % self.prime text_hash = (self.base * text_hash + ord(text[i])) % self.prime # Slide the pattern over text for i in range(n - m + 1): # Check if hash values match if pattern_hash == text_hash: # Verify character by character if text[i:i+m] == pattern: matches.append(i) # Calculate hash for next window if i < n - m: text_hash = (self.base * (text_hash - ord(text[i]) * h) + ord(text[i + m])) % self.prime # Handle negative hash value if text_hash < 0: text_hash += self.prime return matches # Z-Algorithm for pattern matching def z_algorithm(s): """Compute Z array for string matching""" n = len(s) z = [0] * n l = r = 0 for i in range(1, n): if i > r: l = r = i while r < n and s[r - l] == s[r]: r += 1 z[i] = r - l r -= 1 else: k = i - l if z[k] < r - i + 1: z[i] = z[k] else: l = i while r < n and s[r - l] == s[r]: r += 1 z[i] = r - l r -= 1 return z def z_search(text, pattern): """Find all occurrences using Z-algorithm""" combined = pattern + "$" + text z = z_algorithm(combined) matches = [] pattern_len = len(pattern) for i in range(pattern_len + 1, len(combined)): if z[i] == pattern_len: matches.append(i - pattern_len - 1) return matches # Example: Longest common substring def longest_common_substring(s1, s2): """Find the longest common substring of two strings""" m, n = len(s1), len(s2) dp = [[0] * (n + 1) for _ in range(m + 1)] max_len = 0 ending_pos = 0 for i in range(1, m + 1): for j in range(1, n + 1): if s1[i-1] == s2[j-1]: dp[i][j] = dp[i-1][j-1] + 1 if dp[i][j] > max_len: max_len = dp[i][j] ending_pos = i return s1[ending_pos - max_len:ending_pos]
OCaml
(* KMP Algorithm *) module KMP = struct let build_failure_function pattern = let m = String.length pattern in let failure = Array.make m 0 in let j = ref 0 in for i = 1 to m - 1 do while !j > 0 && pattern.[i] <> pattern.[!j] do j := failure.(!j - 1) done; if pattern.[i] = pattern.[!j] then incr j; failure.(i) <- !j done; failure let search text pattern = if String.length pattern = 0 then [] else let n = String.length text in let m = String.length pattern in let failure = build_failure_function pattern in let matches = ref [] in let j = ref 0 in for i = 0 to n - 1 do while !j > 0 && text.[i] <> pattern.[!j] do j := failure.(!j - 1) done; if text.[i] = pattern.[!j] then incr j; if !j = m then begin matches := (i - m + 1) :: !matches; j := failure.(!j - 1) end done; List.rev !matches end (* Rabin-Karp Algorithm *) module RabinKarp = struct let base = 256 let prime = 101 let hash_string s start len = let rec hash i acc = if i >= start + len then acc else let acc' = (base * acc + Char.code s.[i]) mod prime in hash (i + 1) acc' in hash start 0 let search text pattern = let n = String.length text in let m = String.length pattern in let matches = ref [] in if m <= n then begin let pattern_hash = hash_string pattern 0 m in let text_hash = ref (hash_string text 0 m) in (* Calculate h = base^(m-1) mod prime *) let h = ref 1 in for i = 1 to m - 1 do h := (!h * base) mod prime done; for i = 0 to n - m do if !text_hash = pattern_hash then begin (* Verify character by character *) let mismatch = ref false in for j = 0 to m - 1 do if text.[i + j] <> pattern.[j] then mismatch := true done; if not !mismatch then matches := i :: !matches end; (* Calculate hash for next window *) if i < n - m then begin text_hash := (base * (!text_hash - Char.code text.[i] * !h) + Char.code text.[i + m]) mod prime; if !text_hash < 0 then text_hash := !text_hash + prime end done end; List.rev !matches end (* Z-Algorithm *) module ZAlgorithm = struct let compute_z_array s = let n = String.length s in let z = Array.make n 0 in let l = ref 0 in let r = ref 0 in for i = 1 to n - 1 do if i > !r then begin l := i; r := i; while !r < n && s.[!r - !l] = s.[!r] do incr r done; z.(i) <- !r - !l; decr r end else begin let k = i - !l in if z.(k) < !r - i + 1 then z.(i) <- z.(k) else begin l := i; while !r < n && s.[!r - !l] = s.[!r] do incr r done; z.(i) <- !r - !l; decr r end end done; z let search text pattern = let combined = pattern ^ "$" ^ text in let z = compute_z_array combined in let pattern_len = String.length pattern in let matches = ref [] in for i = pattern_len + 1 to String.length combined - 1 do if z.(i) = pattern_len then matches := (i - pattern_len - 1) :: !matches done; List.rev !matches end
Algorithm | Preprocessing | Searching | Space | Best For |
---|---|---|---|---|
KMP | O(m) | O(n) | O(m) | No backtracking needed |
Rabin-Karp | O(m) | O(n) average | O(1) | Multiple pattern search |
Z-Algorithm | O(m + n) | O(m + n) | O(m + n) | Pattern variations |
Advanced Sorting
Non-Comparison Based Sorting
While comparison-based sorts have a lower bound of O(n log n), non-comparison sorts like Counting Sort and Radix Sort can achieve O(n) time complexity under specific conditions.
📬 Real-Life Analogy:
Think of sorting mail by ZIP code: instead of comparing each letter with others, you create bins for each ZIP code and distribute letters directly into appropriate bins.
🎯 When to Use:
- Counting Sort: Small range of integers
- Radix Sort: Fixed-width integers or strings
- Bucket Sort: Uniformly distributed data
- External Sort: Data too large for memory
TypeScript
// Counting Sort function countingSort(arr: number[], maxValue: number): number[] { const count = new Array(maxValue + 1).fill(0); const output = new Array(arr.length); // Count occurrences for (const num of arr) { count[num]++; } // Transform to cumulative count for (let i = 1; i <= maxValue; i++) { count[i] += count[i - 1]; } // Build output array (stable sort) for (let i = arr.length - 1; i >= 0; i--) { output[count[arr[i]] - 1] = arr[i]; count[arr[i]]--; } return output; } // Radix Sort function radixSort(arr: number[]): number[] { if (arr.length === 0) return arr; const getMax = (arr: number[]) => Math.max(...arr); const getDigit = (num: number, place: number) => Math.floor(Math.abs(num) / Math.pow(10, place)) % 10; const maxNum = getMax(arr); const maxDigits = Math.floor(Math.log10(maxNum)) + 1; let result = [...arr]; for (let k = 0; k < maxDigits; k++) { const buckets: number[][] = Array.from({ length: 10 }, () => []); for (const num of result) { const digit = getDigit(num, k); buckets[digit].push(num); } result = buckets.flat(); } return result; } // Bucket Sort function bucketSort(arr: number[]): number[] { if (arr.length === 0) return arr; const n = arr.length; const buckets: number[][] = Array.from({ length: n }, () => []); // Find range const minVal = Math.min(...arr); const maxVal = Math.max(...arr); const range = maxVal - minVal; // Distribute to buckets for (const num of arr) { const index = Math.floor(((num - minVal) / range) * (n - 1)); buckets[index].push(num); } // Sort individual buckets and concatenate const result: number[] = []; for (const bucket of buckets) { bucket.sort((a, b) => a - b); result.push(...bucket); } return result; } // External Merge Sort for large datasets class ExternalSort { private chunkSize: number; constructor(chunkSize: number = 1000000) { this.chunkSize = chunkSize; } // Simulate sorting large file sort(data: number[]): number[] { const chunks: number[][] = []; // Step 1: Sort chunks that fit in memory for (let i = 0; i < data.length; i += this.chunkSize) { const chunk = data.slice(i, i + this.chunkSize); chunk.sort((a, b) => a - b); chunks.push(chunk); } // Step 2: K-way merge return this.kWayMerge(chunks); } private kWayMerge(chunks: number[][]): number[] { const result: number[] = []; const indices = new Array(chunks.length).fill(0); // Min heap for efficient minimum finding const heap: [number, number][] = []; // [value, chunk index] // Initialize heap with first element from each chunk for (let i = 0; i < chunks.length; i++) { if (chunks[i].length > 0) { heap.push([chunks[i][0], i]); } } // Min heapify heap.sort((a, b) => a[0] - b[0]); while (heap.length > 0) { const [value, chunkIndex] = heap.shift()!; result.push(value); indices[chunkIndex]++; if (indices[chunkIndex] < chunks[chunkIndex].length) { heap.push([chunks[chunkIndex][indices[chunkIndex]], chunkIndex]); heap.sort((a, b) => a[0] - b[0]); } } return result; } }
Python
# Counting Sort def counting_sort(arr, max_value): """Sort array using counting sort""" count = [0] * (max_value + 1) output = [0] * len(arr) # Count occurrences for num in arr: count[num] += 1 # Transform to cumulative count for i in range(1, max_value + 1): count[i] += count[i - 1] # Build output array (stable sort) for i in range(len(arr) - 1, -1, -1): output[count[arr[i]] - 1] = arr[i] count[arr[i]] -= 1 return output # Radix Sort def radix_sort(arr): """Sort array using radix sort""" if not arr: return arr def get_digit(num, place): return (abs(num) // (10 ** place)) % 10 max_num = max(arr) max_digits = len(str(max_num)) result = arr[:] for digit_place in range(max_digits): buckets = [[] for _ in range(10)] for num in result: digit = get_digit(num, digit_place) buckets[digit].append(num) result = [] for bucket in buckets: result.extend(bucket) return result # Bucket Sort def bucket_sort(arr): """Sort array using bucket sort""" if not arr: return arr n = len(arr) buckets = [[] for _ in range(n)] # Find range min_val = min(arr) max_val = max(arr) range_val = max_val - min_val if range_val == 0: return arr # Distribute to buckets for num in arr: index = int(((num - min_val) / range_val) * (n - 1)) buckets[index].append(num) # Sort individual buckets and concatenate result = [] for bucket in buckets: bucket.sort() result.extend(bucket) return result # External Sort using heapq import heapq class ExternalSort: def __init__(self, chunk_size=1000000): self.chunk_size = chunk_size def sort(self, data): """Sort large dataset using external merge sort""" chunks = [] # Step 1: Sort chunks that fit in memory for i in range(0, len(data), self.chunk_size): chunk = sorted(data[i:i + self.chunk_size]) chunks.append(chunk) # Step 2: K-way merge using heap return self.k_way_merge(chunks) def k_way_merge(self, chunks): """Merge sorted chunks using a min heap""" result = [] heap = [] # Initialize heap with iterators for i, chunk in enumerate(chunks): if chunk: # Push (value, chunk_index, element_index) heapq.heappush(heap, (chunk[0], i, 0)) while heap: value, chunk_idx, elem_idx = heapq.heappop(heap) result.append(value) # Add next element from same chunk if available if elem_idx + 1 < len(chunks[chunk_idx]): next_value = chunks[chunk_idx][elem_idx + 1] heapq.heappush(heap, (next_value, chunk_idx, elem_idx + 1)) return result
OCaml
(* Counting Sort *) let counting_sort arr max_value = let n = Array.length arr in let count = Array.make (max_value + 1) 0 in let output = Array.make n 0 in (* Count occurrences *) Array.iter (fun x -> count.(x) <- count.(x) + 1) arr; (* Transform to cumulative count *) for i = 1 to max_value do count.(i) <- count.(i) + count.(i - 1) done; (* Build output array *) for i = n - 1 downto 0 do let value = arr.(i) in output.(count.(value) - 1) <- value; count.(value) <- count.(value) - 1 done; output (* Radix Sort *) let radix_sort arr = if Array.length arr = 0 then arr else let get_digit num place = (abs num / int_of_float (10.0 ** float_of_int place)) mod 10 in let max_num = Array.fold_left max arr.(0) arr in let max_digits = String.length (string_of_int max_num) in let result = ref (Array.copy arr) in for digit_place = 0 to max_digits - 1 do let buckets = Array.init 10 (fun _ -> ref []) in (* Distribute to buckets *) Array.iter (fun num -> let digit = get_digit num digit_place in buckets.(digit) := num :: !(buckets.(digit)) ) !result; (* Collect from buckets *) let collected = ref [] in Array.iter (fun bucket -> collected := !collected @ (List.rev !bucket) ) buckets; result := Array.of_list !collected done; !result
Algorithm | Time Complexity | Space Complexity | Stable | Constraints |
---|---|---|---|---|
Counting Sort | O(n + k) | O(k) | Yes | k = range of input |
Radix Sort | O(d × n) | O(n) | Yes | d = number of digits |
Bucket Sort | O(n) average | O(n) | Yes | Uniform distribution |
External Sort | O(n log n) | O(M) | Yes | M = memory limit |
Divide & Conquer Algorithms
Advanced Divide & Conquer Techniques
Divide and conquer algorithms solve problems by breaking them into smaller subproblems, solving them independently, and combining results. Advanced techniques optimize the division and combination steps.
🧩 Real-Life Analogy:
Think of assembling a large jigsaw puzzle: divide it into sections (sky, ground, etc.), solve each section independently with different people, then combine the completed sections.
🎯 Key Divide & Conquer Algorithms:
- Closest Pair of Points: Geometric divide and conquer
- Fast Fourier Transform: Signal processing
- Karatsuba Multiplication: Large number multiplication
- Strassen's Matrix Multiplication: Efficient matrix operations
TypeScript
// Closest Pair of Points interface Point { x: number; y: number; } class ClosestPair { private distance(p1: Point, p2: Point): number { return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2); } private bruteForce(points: Point[]): number { let minDist = Infinity; for (let i = 0; i < points.length; i++) { for (let j = i + 1; j < points.length; j++) { minDist = Math.min(minDist, this.distance(points[i], points[j])); } } return minDist; } private stripClosest(strip: Point[], d: number): number { let minDist = d; strip.sort((a, b) => a.y - b.y); for (let i = 0; i < strip.length; i++) { for (let j = i + 1; j < strip.length && (strip[j].y - strip[i].y) < minDist; j++) { minDist = Math.min(minDist, this.distance(strip[i], strip[j])); } } return minDist; } private closestPairRec(px: Point[], py: Point[]): number { const n = px.length; if (n <= 3) { return this.bruteForce(px); } const mid = Math.floor(n / 2); const midpoint = px[mid]; const pyl = py.filter(point => point.x <= midpoint.x); const pyr = py.filter(point => point.x > midpoint.x); const dl = this.closestPairRec(px.slice(0, mid), pyl); const dr = this.closestPairRec(px.slice(mid), pyr); const d = Math.min(dl, dr); const strip = py.filter(point => Math.abs(point.x - midpoint.x) < d); return Math.min(d, this.stripClosest(strip, d)); } findClosestPair(points: Point[]): number { const px = [...points].sort((a, b) => a.x - b.x); const py = [...points].sort((a, b) => a.y - b.y); return this.closestPairRec(px, py); } } // Karatsuba Multiplication class KaratsubaMultiplication { multiply(x: bigint, y: bigint): bigint { if (x < 10n || y < 10n) { return x * y; } const xStr = x.toString(); const yStr = y.toString(); const n = Math.max(xStr.length, yStr.length); const half = Math.floor(n / 2); const multiplier = 10n ** BigInt(half); const high1 = x / multiplier; const low1 = x % multiplier; const high2 = y / multiplier; const low2 = y % multiplier; const z0 = this.multiply(low1, low2); const z1 = this.multiply(low1 + high1, low2 + high2); const z2 = this.multiply(high1, high2); return z2 * (multiplier ** 2n) + (z1 - z2 - z0) * multiplier + z0; } } // Matrix Multiplication (Strassen's Algorithm) class StrassenMatrix { multiply(A: number[][], B: number[][]): number[][] { const n = A.length; if (n <= 2) { return this.standardMultiply(A, B); } const half = Math.floor(n / 2); // Divide matrices into quadrants const A11 = this.getSubMatrix(A, 0, 0, half); const A12 = this.getSubMatrix(A, 0, half, half); const A21 = this.getSubMatrix(A, half, 0, half); const A22 = this.getSubMatrix(A, half, half, half); const B11 = this.getSubMatrix(B, 0, 0, half); const B12 = this.getSubMatrix(B, 0, half, half); const B21 = this.getSubMatrix(B, half, 0, half); const B22 = this.getSubMatrix(B, half, half, half); // Calculate 7 products (Strassen's method) const M1 = this.multiply(this.add(A11, A22), this.add(B11, B22)); const M2 = this.multiply(this.add(A21, A22), B11); const M3 = this.multiply(A11, this.subtract(B12, B22)); const M4 = this.multiply(A22, this.subtract(B21, B11)); const M5 = this.multiply(this.add(A11, A12), B22); const M6 = this.multiply(this.subtract(A21, A11), this.add(B11, B12)); const M7 = this.multiply(this.subtract(A12, A22), this.add(B21, B22)); // Calculate result quadrants const C11 = this.add(this.subtract(this.add(M1, M4), M5), M7); const C12 = this.add(M3, M5); const C21 = this.add(M2, M4); const C22 = this.add(this.subtract(this.add(M1, M3), M2), M6); // Combine quadrants return this.combineMatrices(C11, C12, C21, C22); } private standardMultiply(A: number[][], B: number[][]): number[][] { const n = A.length; const result = Array(n).fill(0).map(() => Array(n).fill(0)); for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { for (let k = 0; k < n; k++) { result[i][j] += A[i][k] * B[k][j]; } } } return result; } private getSubMatrix(matrix: number[][], startRow: number, startCol: number, size: number): number[][] { const result = Array(size).fill(0).map(() => Array(size).fill(0)); for (let i = 0; i < size; i++) { for (let j = 0; j < size; j++) { result[i][j] = matrix[startRow + i][startCol + j]; } } return result; } private add(A: number[][], B: number[][]): number[][] { const n = A.length; const result = Array(n).fill(0).map(() => Array(n).fill(0)); for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { result[i][j] = A[i][j] + B[i][j]; } } return result; } private subtract(A: number[][], B: number[][]): number[][] { const n = A.length; const result = Array(n).fill(0).map(() => Array(n).fill(0)); for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { result[i][j] = A[i][j] - B[i][j]; } } return result; } private combineMatrices(C11: number[][], C12: number[][], C21: number[][], C22: number[][]): number[][] { const half = C11.length; const n = half * 2; const result = Array(n).fill(0).map(() => Array(n).fill(0)); for (let i = 0; i < half; i++) { for (let j = 0; j < half; j++) { result[i][j] = C11[i][j]; result[i][j + half] = C12[i][j]; result[i + half][j] = C21[i][j]; result[i + half][j + half] = C22[i][j]; } } return result; } }
Python
# Closest Pair of Points import math from typing import List, Tuple class Point: def __init__(self, x: float, y: float): self.x = x self.y = y class ClosestPair: def distance(self, p1: Point, p2: Point) -> float: return math.sqrt((p1.x - p2.x)**2 + (p1.y - p2.y)**2) def brute_force(self, points: List[Point]) -> float: min_dist = float('inf') n = len(points) for i in range(n): for j in range(i + 1, n): min_dist = min(min_dist, self.distance(points[i], points[j])) return min_dist def strip_closest(self, strip: List[Point], d: float) -> float: min_dist = d strip.sort(key=lambda p: p.y) for i in range(len(strip)): j = i + 1 while j < len(strip) and (strip[j].y - strip[i].y) < min_dist: min_dist = min(min_dist, self.distance(strip[i], strip[j])) j += 1 return min_dist def closest_pair_rec(self, px: List[Point], py: List[Point]) -> float: n = len(px) if n <= 3: return self.brute_force(px) mid = n // 2 midpoint = px[mid] pyl = [point for point in py if point.x <= midpoint.x] pyr = [point for point in py if point.x > midpoint.x] dl = self.closest_pair_rec(px[:mid], pyl) dr = self.closest_pair_rec(px[mid:], pyr) d = min(dl, dr) strip = [point for point in py if abs(point.x - midpoint.x) < d] return min(d, self.strip_closest(strip, d)) def find_closest_pair(self, points: List[Point]) -> float: px = sorted(points, key=lambda p: p.x) py = sorted(points, key=lambda p: p.y) return self.closest_pair_rec(px, py) # Karatsuba Multiplication class KaratsubaMultiplication: def multiply(self, x: int, y: int) -> int: if x < 10 or y < 10: return x * y n = max(len(str(x)), len(str(y))) half = n // 2 multiplier = 10 ** half high1, low1 = divmod(x, multiplier) high2, low2 = divmod(y, multiplier) z0 = self.multiply(low1, low2) z1 = self.multiply(low1 + high1, low2 + high2) z2 = self.multiply(high1, high2) return z2 * (multiplier ** 2) + (z1 - z2 - z0) * multiplier + z0 # Matrix Multiplication (Strassen's Algorithm) class StrassenMatrix: def multiply(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]: n = len(A) if n <= 2: return self.standard_multiply(A, B) half = n // 2 # Divide matrices into quadrants A11 = self.get_sub_matrix(A, 0, 0, half) A12 = self.get_sub_matrix(A, 0, half, half) A21 = self.get_sub_matrix(A, half, 0, half) A22 = self.get_sub_matrix(A, half, half, half) B11 = self.get_sub_matrix(B, 0, 0, half) B12 = self.get_sub_matrix(B, 0, half, half) B21 = self.get_sub_matrix(B, half, 0, half) B22 = self.get_sub_matrix(B, half, half, half) # Calculate 7 products (Strassen's method) M1 = self.multiply(self.add(A11, A22), self.add(B11, B22)) M2 = self.multiply(self.add(A21, A22), B11) M3 = self.multiply(A11, self.subtract(B12, B22)) M4 = self.multiply(A22, self.subtract(B21, B11)) M5 = self.multiply(self.add(A11, A12), B22) M6 = self.multiply(self.subtract(A21, A11), self.add(B11, B12)) M7 = self.multiply(self.subtract(A12, A22), self.add(B21, B22)) # Calculate result quadrants C11 = self.add(self.subtract(self.add(M1, M4), M5), M7) C12 = self.add(M3, M5) C21 = self.add(M2, M4) C22 = self.add(self.subtract(self.add(M1, M3), M2), M6) # Combine quadrants return self.combine_matrices(C11, C12, C21, C22) def standard_multiply(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]: n = len(A) result = [[0] * n for _ in range(n)] for i in range(n): for j in range(n): for k in range(n): result[i][j] += A[i][k] * B[k][j] return result def get_sub_matrix(self, matrix: List[List[int]], start_row: int, start_col: int, size: int) -> List[List[int]]: return [[matrix[start_row + i][start_col + j] for j in range(size)] for i in range(size)] def add(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]: n = len(A) return [[A[i][j] + B[i][j] for j in range(n)] for i in range(n)] def subtract(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]: n = len(A) return [[A[i][j] - B[i][j] for j in range(n)] for i in range(n)] def combine_matrices(self, C11, C12, C21, C22): half = len(C11) n = half * 2 result = [[0] * n for _ in range(n)] for i in range(half): for j in range(half): result[i][j] = C11[i][j] result[i][j + half] = C12[i][j] result[i + half][j] = C21[i][j] result[i + half][j + half] = C22[i][j] return result
OCaml
(* Closest Pair of Points *) module ClosestPair = struct type point = { x: float; y: float } let distance p1 p2 = sqrt ((p1.x -. p2.x) ** 2.0 +. (p1.y -. p2.y) ** 2.0) let brute_force points = let rec loop i j min_dist = if i >= Array.length points then min_dist else if j >= Array.length points then loop (i + 1) (i + 2) min_dist else let dist = distance points.(i) points.(j) in loop i (j + 1) (min min_dist dist) in if Array.length points < 2 then infinity else loop 0 1 infinity let strip_closest strip d = Array.sort (fun p1 p2 -> compare p1.y p2.y) strip; let min_dist = ref d in for i = 0 to Array.length strip - 1 do let j = ref (i + 1) in while !j < Array.length strip && (strip.(!j).y -. strip.(i).y) < !min_dist do min_dist := min !min_dist (distance strip.(i) strip.(!j)); incr j done done; !min_dist let rec closest_pair_rec px py = let n = Array.length px in if n <= 3 then brute_force px else let mid = n / 2 in let midpoint = px.(mid) in let pyl = Array.of_list (List.filter (fun p -> p.x <= midpoint.x) (Array.to_list py)) in let pyr = Array.of_list (List.filter (fun p -> p.x > midpoint.x) (Array.to_list py)) in let dl = closest_pair_rec (Array.sub px 0 mid) pyl in let dr = closest_pair_rec (Array.sub px mid (n - mid)) pyr in let d = min dl dr in let strip = Array.of_list (List.filter (fun p -> abs_float (p.x -. midpoint.x) < d) (Array.to_list py)) in min d (strip_closest strip d) let find_closest_pair points = let px = Array.copy points in let py = Array.copy points in Array.sort (fun p1 p2 -> compare p1.x p2.x) px; Array.sort (fun p1 p2 -> compare p1.y p2.y) py; closest_pair_rec px py end (* Karatsuba Multiplication *) module Karatsuba = struct let rec multiply x y = if x < 10 || y < 10 then x * y else let x_str = string_of_int x in let y_str = string_of_int y in let n = max (String.length x_str) (String.length y_str) in let half = n / 2 in let multiplier = int_of_float (10.0 ** float_of_int half) in let high1 = x / multiplier in let low1 = x mod multiplier in let high2 = y / multiplier in let low2 = y mod multiplier in let z0 = multiply low1 low2 in let z1 = multiply (low1 + high1) (low2 + high2) in let z2 = multiply high1 high2 in z2 * (multiplier * multiplier) + (z1 - z2 - z0) * multiplier + z0 end
Algorithm | Time Complexity | Traditional | Improvement |
---|---|---|---|
Closest Pair | O(n log n) | O(n²) | Divide plane by x-coordinate |
Karatsuba | O(n^1.585) | O(n²) | 3 multiplications instead of 4 |
Strassen's | O(n^2.807) | O(n³) | 7 multiplications instead of 8 |
FFT | O(n log n) | O(n²) | Divide by even/odd indices |