|
1 | 1 | package com.thealgorithms.randomized;
|
2 |
| -import java.math.BigDecimal; |
3 |
| -import java.math.RoundingMode; |
4 | 2 | import java.util.ArrayList;
|
5 | 3 | import java.util.Collections;
|
6 |
| -import java.util.Comparator; |
| 4 | +import java.util.HashMap; |
| 5 | +import java.util.HashSet; |
7 | 6 | import java.util.List;
|
| 7 | +import java.util.Map; |
| 8 | +import java.util.Random; |
| 9 | +import java.util.Set; |
8 | 10 |
|
9 |
| -// As required by Repository, new algorithms have URL in comments with explanation |
10 |
| -// https://www.geeksforgeeks.org/closest-pair-of-points-using-divide-and-conquer-algorithm |
11 |
| -// Given 2 or more points on a 2-dimensional plane, find the closest 2 points in Euclidean distance |
12 |
| -// This class uses the divide and conquer technique with recursion |
| 11 | +class Point { |
| 12 | + double x, y; |
13 | 13 |
|
14 |
| -final class Point implements Comparable<Point> { |
15 |
| - double x; |
16 |
| - double y; |
17 |
| - |
18 |
| - // Constructor to initialize a point with x and y coordinates |
19 |
| - Point(double x, double y) { |
| 14 | + public Point(double x, double y) { |
20 | 15 | this.x = x;
|
21 | 16 | this.y = y;
|
22 | 17 | }
|
23 | 18 |
|
24 |
| - public int compareTo(Point other) { |
25 |
| - return Double.compare(this.x, other.x); |
26 |
| - } |
27 |
| - |
28 |
| - static double distance(Point p1, Point p2) { |
29 |
| - return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); |
| 19 | + @Override |
| 20 | + public String toString() { |
| 21 | + return "(" + x + ", " + y + ")"; |
30 | 22 | }
|
31 | 23 | }
|
32 | 24 |
|
33 |
| -public final class ClosestPair { |
34 |
| - // Private constructor to prevent instantiation |
35 |
| - private ClosestPair() { |
36 |
| - throw new AssertionError("Utility class should not be instantiated."); |
37 |
| - } |
38 |
| - |
39 |
| - public static double closest(List<Point> points) { |
40 |
| - if (points == null || points.isEmpty()) { |
41 |
| - throw new IllegalArgumentException("There are no pairs to compare."); |
42 |
| - } |
43 |
| - |
44 |
| - if (points.size() == 1) { |
45 |
| - throw new IllegalArgumentException("There is only one pair."); |
46 |
| - } |
47 |
| - |
48 |
| - Collections.sort(points); |
49 |
| - double result = closestRecursiveHelper(points, 0, points.size() - 1); |
| 25 | +public class ClosestPair { |
| 26 | + private static final double INFINITY = Double.MAX_VALUE; |
50 | 27 |
|
51 |
| - // Return distance of closest pair rounded to 2 decimal places |
52 |
| - return new BigDecimal(String.valueOf(result)).setScale(2, RoundingMode.HALF_UP).doubleValue(); |
| 28 | + public static double euclideanDistance(Point p1, Point p2) { |
| 29 | + return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); |
53 | 30 | }
|
54 | 31 |
|
55 |
| - private static double closestRecursiveHelper(List<Point> points, int left, int right) { |
56 |
| - // Base Case occurs with 3 or fewer points |
57 |
| - if (right - left <= 2) { |
58 |
| - return baseCase(points, left, right); |
| 32 | + /** |
| 33 | + * Algorithm Proof https://www.cs.toronto.edu/~anikolov/CSC473W20/kt-rabin.pdf |
| 34 | + * Additional information: https://en.wikipedia.org/wiki/Closest_pair_of_points_problem |
| 35 | + * This class uses Rabin's randomized approach to find the closest pair of points. |
| 36 | + * Rabin's approach randomly selects a sample of points to estimate an initial closest distance |
| 37 | + * (delta), then uses a grid for "probabilistic refinement". Finally, it updates the closest pair |
| 38 | + * with the closest distance. |
| 39 | + */ |
| 40 | + |
| 41 | + public static Object[] rabinRandomizedClosestPair(List<Point> points) { |
| 42 | + // Error handling, must have at least 2 points |
| 43 | + if (points == null || points.size() < 2) { |
| 44 | + return new Object[] {null, null, INFINITY}; |
59 | 45 | }
|
60 | 46 |
|
61 |
| - // Divide and conquer |
62 |
| - int mid = (left + right) / 2; |
63 |
| - double midX = points.get(mid).x; |
64 |
| - |
65 |
| - double leftDist = closestRecursiveHelper(points, left, mid); |
66 |
| - double rightDist = closestRecursiveHelper(points, mid + 1, right); |
| 47 | + Collections.shuffle(points, new Random()); // shuffle for required randomness |
67 | 48 |
|
68 |
| - double minDist = Math.min(leftDist, rightDist); |
| 49 | + double delta = INFINITY; // initialize distance |
| 50 | + Point closestA = null; |
| 51 | + Point closestB = null; |
69 | 52 |
|
70 |
| - return checkBoundary(points, left, right, midX, minDist); |
71 |
| - } |
| 53 | + // without exceeding number of points, work with some sample |
| 54 | + int sampleSize = Math.min(7, points.size()); |
72 | 55 |
|
73 |
| - private static double baseCase(List<Point> points, int left, int right) { |
74 |
| - // Sub-problems fitting the base case can use brute force |
75 |
| - double minDist = Double.MAX_VALUE; |
76 |
| - for (int i = left; i <= right; i++) { |
77 |
| - for (int j = i + 1; j <= right; j++) { |
78 |
| - minDist = Math.min(minDist, Point.distance(points.get(i), points.get(j))); |
79 |
| - } |
| 56 | + Random random = new Random(); // select randomly |
| 57 | + Set<Point> sampleSet = new HashSet<>(); // ensure unique pairs |
| 58 | + while (sampleSet.size() < sampleSize) { |
| 59 | + sampleSet.add(points.get(random.nextInt(points.size()))); |
80 | 60 | }
|
81 |
| - return minDist; |
82 |
| - } |
83 |
| - |
84 |
| - private static double checkBoundary(List<Point> points, int left, int right, double midX, double minDist) { |
85 |
| - // Consider a boundary by the dividing line |
86 |
| - List<Point> boundary = new ArrayList<>(); |
87 |
| - for (int i = left; i <= right; i++) { |
88 |
| - if (Math.abs(points.get(i).x - midX) < minDist) { |
89 |
| - boundary.add(points.get(i)); |
| 61 | + List<Point> sample = new ArrayList<>(sampleSet); |
| 62 | + |
| 63 | + // initially the closest points are found via brute force |
| 64 | + for (int i = 0; i < sample.size(); i++) { |
| 65 | + for (int j = i + 1; j < sample.size(); j++) { |
| 66 | + double dist = euclideanDistance(sample.get(i), sample.get(j)); |
| 67 | + if (dist < delta) { |
| 68 | + closestA = sample.get(i); |
| 69 | + closestB = sample.get(j); |
| 70 | + delta = dist; // update distance |
| 71 | + } |
90 | 72 | }
|
91 | 73 | }
|
92 | 74 |
|
93 |
| - // sort by y coordinate within the boundary and check for closer points |
94 |
| - boundary.sort(Comparator.comparingDouble(p -> p.y)); |
95 |
| - for (int i = 0; i < boundary.size(); i++) { |
96 |
| - for (int j = i + 1; j < boundary.size() && (boundary.get(j).y - boundary.get(i).y) < minDist; j++) { |
97 |
| - minDist = Math.min(minDist, Point.distance(boundary.get(i), boundary.get(j))); |
| 75 | + // Create a grid, We will use "Probabilistic Filtering" by only checking |
| 76 | + // neighboring grids to prevent bruteforce checking outside initialization |
| 77 | + Map<String, Point> grid = new HashMap<>(); |
| 78 | + |
| 79 | + // coordinates computed based on delta, estimated closest distance |
| 80 | + for (Point p : points) { |
| 81 | + int gridX = (int) (p.x / delta); |
| 82 | + int gridY = (int) (p.y / delta); |
| 83 | + String key = gridX + "," + gridY; // string for indexing |
| 84 | + |
| 85 | + // check neighboring cells |
| 86 | + for (int dX = -1; dX <= 1; dX++) { |
| 87 | + for (int dY = -1; dY <= 1; dY++) { |
| 88 | + String neighborKey = (gridX + dX) + "," + (gridY + dY); |
| 89 | + Point neighborValue = grid.get(neighborKey); |
| 90 | + |
| 91 | + // update points only if valid neighbor |
| 92 | + if (neighborValue != null && p != neighborValue) { |
| 93 | + double dist = euclideanDistance(p, neighborValue); |
| 94 | + if (dist < delta) { |
| 95 | + closestA = p; |
| 96 | + closestB = neighborValue; |
| 97 | + delta = dist; |
| 98 | + } |
| 99 | + } |
| 100 | + } |
98 | 101 | }
|
| 102 | + grid.put(key, p); |
99 | 103 | }
|
100 |
| - return minDist; |
| 104 | + return new Object[] {closestA, closestB, delta}; |
101 | 105 | }
|
102 | 106 | }
|
0 commit comments