diff --git a/src/main/java/com/williamfiset/algorithms/search/BinarySearch.java b/src/main/java/com/williamfiset/algorithms/search/BinarySearch.java index 29fff3933..f44cc3abe 100644 --- a/src/main/java/com/williamfiset/algorithms/search/BinarySearch.java +++ b/src/main/java/com/williamfiset/algorithms/search/BinarySearch.java @@ -1,86 +1,55 @@ -/** - * If ever you need to do a binary search on discrete values you should use Java's binary search: - * java.util.Arrays.binarySearch(int[] ar, int key) However, in the event that you need to do a - * binary search on the real numbers you can resort to this implementation. - * - *

Time Complexity: O(log(high-low)) - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.search; +package com.example; import java.util.function.DoubleFunction; public class BinarySearch { - // Comparing double values directly is bad practice. - // Using a small epsilon value is the preferred approach - private static final double EPS = 0.00000001; - - public static double binarySearch( - double lo, double hi, double target, DoubleFunction function) { - - if (hi <= lo) throw new IllegalArgumentException("hi should be greater than lo"); + private static final double EPSILON = 0.00000001; + + /** + * Performs binary search on the given function to find a value that is close to the target. + * + * @param lo The lower bound of the search interval. + * @param hi The upper bound of the search interval. + * @param target The target value to find. + * @param function The function to evaluate. + * @return A value close to the target. + * @throws IllegalArgumentException If the upper bound is not greater than the lower bound. + */ + public static double binarySearch(double lo, double hi, double target, DoubleFunction function) { + if (hi <= lo) { + throw new IllegalArgumentException("The upper bound must be greater than the lower bound."); + } double mid; do { - - // Find the middle point mid = (hi + lo) / 2.0; - - // Compute the value of our function for the middle point - // Note that f can be any function not just the square root function double value = function.apply(mid); - if (value > target) { hi = mid; } else { lo = mid; } - - } while ((hi - lo) > EPS); + } while ((hi - lo) > EPSILON); return mid; } public static void main(String[] args) { - - // EXAMPLE #1 - // Suppose we want to know what the square root of 875 is and - // we have no knowledge of the wonderful Math.sqrt() function. - // One approach is to use a binary search because we know that - // the square root of 875 is bounded in the region: [0, 875]. - // - // We can define our function to be f(x) = x*x and our target - // value to be 875. As we binary search on f(x) approaching - // successively closer values of 875 we get better and better - // values of x (the square root of 875) - + // EXAMPLE #1: Finding the square root of a number using binary search. double lo = 0.0; double hi = 875.0; double target = 875.0; - - DoubleFunction function = (x) -> (x * x); - - double sqrtVal = binarySearch(lo, hi, target, function); + DoubleFunction squareFunction = (x) -> (x * x); + double sqrtVal = binarySearch(lo, hi, target, squareFunction); System.out.printf("sqrt(%.2f) = %.5f, x^2 = %.5f\n", target, sqrtVal, (sqrtVal * sqrtVal)); - // EXAMPLE #2 - // Suppose we want to find the radius of a sphere with volume 100m^3 using - // a binary search. We know that for a sphere the volume is given by - // V = (4/3)*pi*r^3, so all we have to do is binary search on the radius. - // - // Note: this is a silly example because you could just solve for r, but it - // shows how binary search can be a powerful technique. - + // EXAMPLE #2: Finding the radius of a sphere with a given volume using binary search. double radiusLowerBound = 0; double radiusUpperBound = 1000; double volume = 100.0; DoubleFunction sphereVolumeFunction = (r) -> ((4.0 / 3.0) * Math.PI * r * r * r); - - double sphereRadius = - binarySearch(radiusLowerBound, radiusUpperBound, volume, sphereVolumeFunction); - + double sphereRadius = binarySearch(radiusLowerBound, radiusUpperBound, volume, sphereVolumeFunction); System.out.printf("Sphere radius = %.5fm\n", sphereRadius); } } diff --git a/src/main/java/com/williamfiset/algorithms/search/InterpolationSearch.java b/src/main/java/com/williamfiset/algorithms/search/InterpolationSearch.java index d03302364..78b5810b0 100644 --- a/src/main/java/com/williamfiset/algorithms/search/InterpolationSearch.java +++ b/src/main/java/com/williamfiset/algorithms/search/InterpolationSearch.java @@ -8,23 +8,27 @@ public class InterpolationSearch { /** - * A fast alternative to a binary search when the elements are uniformly distributed. This - * algorithm runs in a time complexity of ~O(log(log(n))). + * Searches for the given value in an ordered list containing uniformly distributed values using + * interpolation search. * * @param nums - an ordered list containing uniformly distributed values. * @param val - the value we're looking for in 'nums' + * @return the index of the value if it is found, otherwise -1. */ public static int interpolationSearch(int[] nums, int val) { - int lo = 0, mid = 0, hi = nums.length - 1; - while (nums[lo] <= val && nums[hi] >= val) { - mid = lo + ((val - nums[lo]) * (hi - lo)) / (nums[hi] - nums[lo]); + int lo = 0, hi = nums.length - 1; + while (lo <= hi && val >= nums[lo] && val <= nums[hi]) { + // Interpolate the index of the mid-point element to be closer to our target value + int mid = lo + ((val - nums[lo]) * (hi - lo)) / (nums[hi] - nums[lo]); + if (nums[mid] == val) { + return mid; + } if (nums[mid] < val) { lo = mid + 1; - } else if (nums[mid] > val) { + } else { hi = mid - 1; - } else return mid; + } } - if (nums[lo] == val) return lo; return -1; } diff --git a/src/main/java/com/williamfiset/algorithms/search/TernarySearch.java b/src/main/java/com/williamfiset/algorithms/search/TernarySearch.java index 25aee31a1..9d0e4157b 100644 --- a/src/main/java/com/williamfiset/algorithms/search/TernarySearch.java +++ b/src/main/java/com/williamfiset/algorithms/search/TernarySearch.java @@ -12,24 +12,29 @@ */ package com.williamfiset.algorithms.search; -import java.util.function.DoubleFunction; +import java.util.function.DoubleUnaryOperator; public class TernarySearch { // Define a very small epsilon value to compare double values - private static final double EPS = 0.000000001; + private static final double EPSILON = 1e-9; // Perform a ternary search on the interval low to high. // Remember that your function must be a continuous unimodal // function, this means a function which decreases then increases (U shape) - public static double ternarySearch(double low, double high, DoubleFunction function) { - Double best = null; + public static double ternarySearch(double low, double high, DoubleUnaryOperator function) { + double best = Double.NaN; while (true) { - double mid1 = (2 * low + high) / 3, mid2 = (low + 2 * high) / 3; - double res1 = function.apply(mid1), res2 = function.apply(mid2); - if (res1 > res2) low = mid1; - else high = mid2; - if (best != null && Math.abs(best - mid1) < EPS) break; + double mid1 = (2 * low + high) / 3.0, mid2 = (low + 2 * high) / 3.0; + double res1 = function.applyAsDouble(mid1), res2 = function.applyAsDouble(mid2); + if (res1 > res2) { + low = mid1; + } else { + high = mid2; + } + if (!Double.isNaN(best) && Math.abs(best - mid1) < EPSILON) { + break; + } best = mid1; } return best; @@ -39,7 +44,7 @@ public static void main(String[] args) { // Search for the lowest point on the function x^2 + 3x + 5 // using a ternary search on the interval [-100, +100] - DoubleFunction function = (x) -> (x * x + 3 * x + 5); + DoubleUnaryOperator function = (x) -> (x * x + 3 * x + 5); double root = ternarySearch(-100.0, +100.0, function); System.out.printf("%.4f\n", root); }