forked from TheAlgorithms/Java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathConvexHull.java
More file actions
207 lines (178 loc) · 7.59 KB
/
ConvexHull.java
File metadata and controls
207 lines (178 loc) · 7.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package com.thealgorithms.geometry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/**
* A class providing algorithms to compute the convex hull of a set of points
* using brute-force and recursive methods.
*
* Convex hull: The smallest convex polygon that contains all the given points.
*
* Algorithms provided:
* 1. Brute-Force Method
* 2. Recursive (Divide-and-Conquer) Method
*
* @author Hardvan
*/
public final class ConvexHull {
private ConvexHull() {
}
private static boolean checkPointOrientation(Point i, Point j, Point k) {
int detK = Point.orientation(i, j, k);
if (detK > 0) {
return true; // pointsLeftOfIJ
} else if (detK < 0) {
return false; // pointsRightOfIJ
} else {
return k.compareTo(i) >= 0 && k.compareTo(j) <= 0;
}
}
public static List<Point> convexHullBruteForce(List<Point> points) {
Set<Point> convexSet = new TreeSet<>(Comparator.naturalOrder());
for (int i = 0; i < points.size() - 1; i++) {
for (int j = i + 1; j < points.size(); j++) {
boolean allPointsOnOneSide = true;
boolean leftSide = checkPointOrientation(points.get(i), points.get(j), points.get((i + 1) % points.size()));
for (int k = 0; k < points.size(); k++) {
if (k != i && k != j && checkPointOrientation(points.get(i), points.get(j), points.get(k)) != leftSide) {
allPointsOnOneSide = false;
break;
}
}
if (allPointsOnOneSide) {
convexSet.add(points.get(i));
convexSet.add(points.get(j));
}
}
}
return new ArrayList<>(convexSet);
}
/**
* Computes the convex hull using a recursive divide-and-conquer approach.
* Returns points in counter-clockwise order starting from the bottom-most, left-most point.
*
* @param points the input points
* @return the convex hull points in counter-clockwise order
*/
public static List<Point> convexHullRecursive(List<Point> points) {
if (points.size() < 3) {
List<Point> result = new ArrayList<>(points);
Collections.sort(result);
return result;
}
Collections.sort(points);
Set<Point> convexSet = new HashSet<>();
Point leftMostPoint = points.getFirst();
Point rightMostPoint = points.getLast();
convexSet.add(leftMostPoint);
convexSet.add(rightMostPoint);
List<Point> upperHull = new ArrayList<>();
List<Point> lowerHull = new ArrayList<>();
for (int i = 1; i < points.size() - 1; i++) {
int det = Point.orientation(leftMostPoint, rightMostPoint, points.get(i));
if (det > 0) {
upperHull.add(points.get(i));
} else if (det < 0) {
lowerHull.add(points.get(i));
}
}
constructHull(upperHull, leftMostPoint, rightMostPoint, convexSet);
constructHull(lowerHull, rightMostPoint, leftMostPoint, convexSet);
// Convert to list and sort in counter-clockwise order
return sortCounterClockwise(new ArrayList<>(convexSet));
}
private static void constructHull(Collection<Point> points, Point left, Point right, Set<Point> convexSet) {
if (!points.isEmpty()) {
Point extremePoint = null;
int extremePointDistance = Integer.MIN_VALUE;
List<Point> candidatePoints = new ArrayList<>();
for (Point p : points) {
int det = Point.orientation(left, right, p);
if (det > 0) {
candidatePoints.add(p);
if (det > extremePointDistance) {
extremePointDistance = det;
extremePoint = p;
}
}
}
if (extremePoint != null) {
constructHull(candidatePoints, left, extremePoint, convexSet);
convexSet.add(extremePoint);
constructHull(candidatePoints, extremePoint, right, convexSet);
}
}
}
/**
* Sorts convex hull points in counter-clockwise order starting from
* the bottom-most, left-most point.
*
* @param hullPoints the unsorted convex hull points
* @return the points sorted in counter-clockwise order
*/
private static List<Point> sortCounterClockwise(List<Point> hullPoints) {
if (hullPoints.size() <= 2) {
Collections.sort(hullPoints);
return hullPoints;
}
// Find the bottom-most, left-most point (pivot)
Point pivot = hullPoints.getFirst();
for (Point p : hullPoints) {
if (p.y() < pivot.y() || (p.y() == pivot.y() && p.x() < pivot.x())) {
pivot = p;
}
}
// Sort other points by polar angle with respect to pivot
final Point finalPivot = pivot;
List<Point> sorted = new ArrayList<>(hullPoints);
sorted.remove(finalPivot);
sorted.sort((p1, p2) -> {
int crossProduct = Point.orientation(finalPivot, p1, p2);
if (crossProduct == 0) {
// Collinear points: sort by distance from pivot (closer first for convex hull)
long dist1 = distanceSquared(finalPivot, p1);
long dist2 = distanceSquared(finalPivot, p2);
return Long.compare(dist1, dist2);
}
// Positive cross product means p2 is counter-clockwise from p1
// We want counter-clockwise order, so if p2 is CCW from p1, p1 should come first
return -crossProduct;
});
// Build result with pivot first, filtering out intermediate collinear points
List<Point> result = new ArrayList<>();
result.add(finalPivot);
if (!sorted.isEmpty()) {
// This loop iterates through the points sorted by angle.
// For points that are collinear with the pivot, we only want the one that is farthest away.
// The sort places closer points first.
for (int i = 0; i < sorted.size() - 1; i++) {
// Check the orientation of the pivot, the current point, and the next point.
int orientation = Point.orientation(finalPivot, sorted.get(i), sorted.get(i + 1));
// If the orientation is not 0, it means the next point (i+1) is at a new angle.
// Therefore, the current point (i) must be the farthest point at its angle. We keep it.
if (orientation != 0) {
result.add(sorted.get(i));
}
// If the orientation is 0, the points are collinear. We discard the current point (i)
// because it is closer to the pivot than the next point (i+1).
}
// Always add the very last point from the sorted list. It is either the only point
// at its angle, or it's the farthest among a set of collinear points.
result.add(sorted.getLast());
}
return result;
}
/**
* Computes the squared distance between two points to avoid floating point operations.
*/
private static long distanceSquared(Point p1, Point p2) {
long dx = (long) p1.x() - p2.x();
long dy = (long) p1.y() - p2.y();
return dx * dx + dy * dy;
}
}