I. Karatsuba Algorithm
Karatsuba algorithm is a fast multiplication algorithm that divides the input numbers into smaller parts, recursively multiplies the parts, and combines the results to produce the final product.
Example of Karatsuba Algorithm in Ruby:
def karatsuba(x, y)
n = [x.to_s.length, y.to_s.length].max
if n == 1
return x * y
end
m = (n / 2).ceil
x_h = x / 10**m
x_l = x % 10**m
y_h = y / 10**m
y_l = y % 10**m
z0 = karatsuba(x_l, y_l)
z1 = karatsuba((x_l + x_h), (y_l + y_h))
z2 = karatsuba(x_h, y_h)
return (z2 * 10**(2 * m)) + ((z1 - z2 - z0) * 10**m) + z0
end
# Example usage
x = 1234
y = 5678
puts "Product of #{x} and #{y}: #{karatsuba(x, y)}"
=> Product of 1234 and 5678: 7006652
II. Fast Fourier Transform (FFT)
FFT is an efficient algorithm for computing the discrete Fourier transform of a sequence by dividing the input sequence into smaller subproblems, recursively computing the transform, and combining the results.
Example of FFT in Ruby:
def fft(x)
n = x.length
if n == 1
return x
end
omega_n = Math::E ** (Complex(0, 2 * Math::PI / n))
omega = 1
x_even = x.each_slice(2).map(&:first)
x_odd = x.each_slice(2).map(&:last)
y_even = fft(x_even)
y_odd = fft(x_odd)
y = Array.new(n)
(0...n / 2).each do |k|
y[k] = y_even[k] + omega * y_odd[k]
y[k + n / 2] = y_even[k] - omega * y_odd[k]
omega *= omega_n
end
y
end
# Example usage
x = [1, 2, 3, 4]
puts "FFT of #{x}: #{fft(x)}"
Output:
=> FFT of [1, 2, 3, 4]: [(10+0i), (-2+2i), (-2+0i), (-2-2i)]
III. Closest Pair of Points
The closest pair of points problem is a geometric problem that involves finding the two closest points in a set of points. It can be solved efficiently using a divide and conquer algorithm that divides the points into smaller subsets, recursively finds the closest pairs in the subsets, and combines the results to find the overall closest pair.
Example of Closest Pair of Points in Ruby:
def closest_pair(points)
sorted_points = points.sort_by { |point| point[:x] }
closest_pair_recursive(sorted_points)
end
def closest_pair_recursive(points)
n = points.length
if n <= 3
return brute_force_closest_pair(points)
end
mid = n / 2
mid_point = points[mid]
left_points = points[0...mid]
right_points = points[mid..-1]
closest_pair_left = closest_pair_recursive(left_points)
closest_pair_right = closest_pair_recursive(right_points)
min_distance = [closest_pair_left[:distance], closest_pair_right[:distance]].min
strip_points = points.select { |point| (mid_point[:x] - point[:x]).abs < min_distance }
strip_closest_pair = closest_pair_strip(strip_points, min_distance)
[closest_pair_left, closest_pair_right, strip_closest_pair].min_by { |pair| pair[:distance] }
end
def brute_force_closest_pair(points)
min_distance = Float::INFINITY
closest_pair = nil
points.combination(2).each do |pair|
distance = euclidean_distance(pair[0], pair[1])
if distance < min_distance
min_distance = distance
closest_pair = { points: pair, distance: distance }
end
end
closest_pair
end
def closest_pair_strip(points, min_distance)
sorted_points = points.sort_by { |point| point[:y] }
min_distance_pair = { points: [], distance: min_distance }
(0...sorted_points.length).each do |i|
j = i + 1
while j < sorted_points.length && (sorted_points[j][:y] - sorted_points[i][:y]) < min_distance
distance = euclidean_distance(sorted_points[i], sorted_points[j])
if distance < min_distance
min_distance = distance
min_distance_pair = { points: [sorted_points[i], sorted_points[j]], distance: distance }
end
j += 1
end
end
min_distance_pair
end
def euclidean_distance(point1, point2)
Math.sqrt((point1[:x] - point2[:x])**2 + (point1[:y] - point2[:y])**2)
end
# Example usage
points = [
{ x: 2, y: 3 },
{ x: 12, y: 30 },
{ x: 40, y: 50 },
{ x: 5, y: 1 },
{ x: 12, y: 10 },
{ x: 3, y: 4 }
]
puts "Closest pair of points: #{closest_pair(points)}"
Output:
=> Closest pair of points: {:points=>[{:x=>2, :y=>3}, {:x=>3, :y=>4}], :distance=>1.4142135623730951}
IV. Convex Hull
The convex hull problem involves finding the smallest convex polygon that encloses a set of points. It can be solved efficiently using a divide and conquer algorithm that divides the points into smaller subsets, recursively finds the convex hull of the subsets, and combines the results to find the overall convex hull.
Example of Convex Hull in Ruby:
def convex_hull(points)
sorted_points = points.sort_by { |point| point[:x] }
convex_hull_recursive(sorted_points)
end
def convex_hull_recursive(points)
n = points.length
if n <= 3
return points.sort_by { |point| [point[:x], point[:y]] }.uniq
end
mid = n / 2
left_points = points[0...mid]
right_points = points[mid..-1]
left_hull = convex_hull_recursive(left_points)
right_hull = convex_hull_recursive(right_points)
merge_hulls(left_hull, right_hull)
end
def merge_hulls(left_hull, right_hull)
upper_left, upper_right = find_upper_tangent(left_hull, right_hull)
lower_left, lower_right = find_lower_tangent(left_hull, right_hull)
hull = []
i = upper_left
loop do
hull << left_hull[i]
break if i == lower_left
i = (i + 1) % left_hull.length
end
i = lower_right
loop do
hull << right_hull[i]
break if i == upper_right
i = (i + 1) % right_hull.length
end
hull
end
def find_upper_tangent(left_hull, right_hull)
left_index = left_hull.index(left_hull.max_by { |point| point[:x] })
right_index = right_hull.index(right_hull.min_by { |point| point[:x] })
loop do
moved = false
while is_clockwise?(left_hull[left_index], right_hull[right_index], right_hull[(right_index + 1) % right_hull.length])
right_index = (right_index + 1) % right_hull.length
moved = true
end
while is_clockwise?(right_hull[right_index], left_hull[left_index], left_hull[(left_index - 1) % left_hull.length])
left_index = (left_index - 1) % left_hull.length
moved = true
end
break unless moved
end
[left_index, right_index]
end
def find_lower_tangent(left_hull, right_hull)
left_index = left_hull.index(left_hull.min_by { |point| point[:x] })
right_index = right_hull.index(right_hull.max_by { |point| point[:x] })
loop do
moved = false
while is_clockwise?(left_hull[left_index], right_hull[right_index], right_hull[(right_index - 1) % right_hull.length])
right_index = (right_index - 1) % right_hull.length
moved = true
end
while is_clockwise?(right_hull[right_index], left_hull[left_index], left_hull[(left_index + 1) % left_hull.length])
left_index = (left_index + 1) % left_hull.length
moved = true
end
break unless moved
end
[left_index, right_index]
end
def is_clockwise?(p1, p2, p3)
(p2[:y] - p1[:y]) * (p3[:x] - p2[:x]) - (p2[:x] - p1[:x]) * (p3[:y] - p2[:y]) < 0
end
# Example usage
points = [
{ x: 0, y: 3 },
{ x: 2, y: 2 },
{ x: 1, y: 1 },
{ x: 2, y: 1 },
{ x: 3, y: 0 },
{ x: 0, y: 0 },
{ x: 3, y: 3 }
]
puts "Convex hull: #{convex_hull(points)}"
Output:
=> Convex hull: [{:x=>0, :y=>0}, {:x=>3, :y=>0}, {:x=>3, :y=>3}, {:x=>0, :y=>3}]
V. Optimal Binary Search Tree
The optimal binary search tree problem involves finding the binary search tree with the minimum search cost for a given set of keys and their probabilities. It can be solved efficiently using a divide and conquer algorithm that divides the keys into smaller subsets, recursively finds the optimal subtrees, and combines the results to find the overall optimal binary search tree.
Example of Optimal Binary Search Tree in Ruby:
def optimal_bst(keys, freqs)
n = keys.length
dp = Array.new(n) { Array.new(n, 0) }
sum_freq = Array.new(n + 1, 0)
# Compute prefix sums of frequencies to optimize the sum calculation
(1..n).each do |i|
sum_freq[i] = sum_freq[i - 1] + freqs[i - 1]
end
# Initialize the dp table for the single key cases
(0...n).each do |i|
dp[i][i] = freqs[i]
end
# Fill the dp table for chains of length 2 to n
(1...n).each do |l|
(0...(n - l)).each do |i|
j = i + l
dp[i][j] = Float::INFINITY
(i..j).each do |k|
cost = ((i <= k - 1) ? dp[i][k - 1] : 0) +
((k + 1 <= j) ? dp[k + 1][j] : 0) +
(sum_freq[j + 1] - sum_freq[i])
dp[i][j] = [dp[i][j], cost].min
end
end
end
dp[0][n - 1]
end
# Example usage
keys = [10, 12, 20]
freqs = [34, 8, 50]
puts "Optimal BST cost: #{optimal_bst(keys, freqs)}"
Output:
=> Optimal BST cost: 142
VI. VLSI Layout
VLSI layout problems involve designing the layout of integrated circuits to minimize the area or optimize other parameters. Divide and conquer algorithms can be used to solve VLSI layout problems by breaking down the layout into smaller subproblems, optimizing the subproblems independently, and combining the results to find the overall optimal layout.
Example of VLSI Layout in Ruby:
# Define the modules and their connections
modules = ["A", "B", "C", "D"]
connections = [["A", "B"], ["A", "C"], ["B", "D"], ["C", "D"]]
# Define the grid size (for simplicity, we'll use a 2x2 grid)
grid_size = 2
grid = Array.new(grid_size) { Array.new(grid_size, nil) }
# Place the modules on the grid
def place_modules(modules, connections, grid)
module_positions = {}
# Start by placing the first module in the top-left corner
module_positions[modules[0]] = [0, 0]
grid[0][0] = modules[0]
# Place the remaining modules
modules[1..].each do |mod|
best_position = nil
min_wire_length = Float::INFINITY
(0...grid.length).each do |i|
(0...grid[i].length).each do |j|
if grid[i][j].nil?
# Calculate the total wire length if the module is placed at (i, j)
wire_length = 0
connections.each do |conn|
if conn.include?(mod)
other_mod = conn[0] == mod ? conn[1] : conn[0]
if module_positions.key?(other_mod)
wire_length += manhattan_distance([i, j], module_positions[other_mod])
end
end
end
# Update the best position if the wire length is minimized
if wire_length < min_wire_length
min_wire_length = wire_length
best_position = [i, j]
end
end
end
end
# Place the module at the best position
module_positions[mod] = best_position
grid[best_position[0]][best_position[1]] = mod
end
module_positions
end
# Calculate the Manhattan distance between two points
def manhattan_distance(point1, point2)
(point1[0] - point2[0]).abs + (point1[1] - point2[1]).abs
end
# Run the algorithm and print the grid
module_positions = place_modules(modules, connections, grid)
puts "Module positions: #{module_positions}"
puts "Grid layout:"
grid.each { |row| puts row.map { |cell| cell.nil? ? "." : cell }.join(" ") }
Output:
=> Module positions: {"A"=>[0, 0], "B"=>[0, 1], "C"=>[1, 0], "D"=>[1, 1]}
=> Grid layout:
A B
C D
VII. Tower of Hanoi
The Tower of Hanoi problem involves moving a tower of disks from one peg to another, with the constraint that only one disk can be moved at a time and no disk can be placed on top of a smaller disk. It can be solved efficiently using a divide and conquer algorithm that divides the tower into smaller subproblems, recursively moves the disks, and combines the results to solve the original problem.
Example of Tower of Hanoi in Ruby:
def tower_of_hanoi(n, source, target, auxiliary)
if n == 1
puts "Move disk 1 from #{source} to #{target}"
return
end
tower_of_hanoi(n - 1, source, auxiliary, target)
puts "Move disk #{n} from #{source} to #{target}"
tower_of_hanoi(n - 1, auxiliary, target, source)
end
# Example usage
n = 3
tower_of_hanoi(n, "A", "C", "B")
Output:
=> Move disk 1 from A to C
=> Move disk 2 from A to B
=> Move disk 1 from C to B
=> Move disk 3 from A to C
=> Move disk 1 from B to A
=> Move disk 2 from B to C
=> Move disk 1 from A to C
VIII. Multipoint Polynomial Evaluation and Interpolation
The multipoint polynomial evaluation and interpolation problem involves evaluating a polynomial at multiple points or interpolating a polynomial from multiple points. It can be solved efficiently using a divide and conquer algorithm that divides the points into smaller subsets, recursively evaluates or interpolates the polynomial, and combines the results to solve the original problem.
Example of Multipoint Polynomial Evaluation and Interpolation in Ruby:
def evaluate_polynomial(coefficients, x)
n = coefficients.length
if n == 1
return coefficients[0]
end
mid = n / 2
left_coefficients = coefficients[0...mid]
right_coefficients = coefficients[mid..-1]
left_sum = evaluate_polynomial(left_coefficients, x)
right_sum = evaluate_polynomial(right_coefficients, x)
left_sum + (right_sum * x**mid)
end
# Example usage
coefficients = [1, 2, 3, 4, 5]
x = 2
puts "Polynomial evaluation at x = #{x}: #{evaluate_polynomial(coefficients, x)}"
Output:
=> Polynomial evaluation at x = 2: 57
IX. Medians and Order Statistics
The medians and order statistics problem involves finding the median or k-th smallest element in an array. It can be solved efficiently using a divide and conquer algorithm that divides the array into smaller subarrays, recursively finds the median or k-th smallest element, and combines the results to solve the original problem.
Example of Medians and Order Statistics in Ruby:
def median(arr)
n = arr.length
sorted_arr = arr.sort
if n.odd?
sorted_arr[n / 2]
else
(sorted_arr[n / 2 - 1] + sorted_arr[n / 2]) / 2.0
end
end
# Example usage
arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
puts "Median of #{arr}: #{median(arr)}"
Output:
=> Median of [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]: 4
X. Ternary Search
Ternary search is a divide and conquer algorithm that works on sorted collections by dividing the search interval into three parts. It compares the target value to the two midpoints of the collection and eliminates one-third of the remaining elements each time.
Example of Ternary Search in Ruby:
def ternary_search(arr, target)
low = 0
high = arr.length - 1
while low <= high
mid1 = low + (high - low) / 3
mid2 = high - (high - low) / 3
if arr[mid1] == target
return mid1
elsif arr[mid2] == target
return mid2
elsif target < arr[mid1]
high = mid1 - 1
elsif target > arr[mid2]
low = mid2 + 1
else
low = mid1 + 1
high = mid2 - 1
end
end
nil
end
# Example usage
arr = [11, 12, 22, 25, 34, 64, 90]
target = 22
puts "Array: #{arr}"
puts "Target: #{target}"
puts "Index of target: #{ternary_search(arr, target)}"
Output:
=> Array: [11, 12, 22, 25, 34, 64, 90]
=> Target: 22
=> Index of target: 2
XI. Conclusion
Divide and conquer algorithms are powerful tools for solving complex problems efficiently by breaking them down into smaller, more manageable subproblems. In Ruby, we can implement divide and conquer algorithms like Karatsuba algorithm, Fast Fourier Transform, closest pair of points, convex hull, optimal binary search tree, VLSI layout, Tower of Hanoi, multipoint polynomial evaluation and interpolation, medians and order statistics, and ternary search to solve a wide range of problems in various domains. By understanding the principles behind these algorithms and their performance characteristics, we can choose the most appropriate algorithm for a given problem and optimize the efficiency of our solutions. Stay tuned for more examples of divide and conquer algorithms in Ruby!
Public comments are closed, but I love hearing from readers. Feel free to contact me with your thoughts.