105 lines
3.4 KiB
Java
105 lines
3.4 KiB
Java
|
import java.util.*;
|
||
|
|
||
|
public class ConvexHull {
|
||
|
|
||
|
public static Point[] convexHull(Point[] points) {
|
||
|
Arrays.sort(points, (a, b) -> Integer.compare(a.x, b.x) != 0 ? Integer.compare(a.x, b.x) : Integer.compare(a.y, b.y));
|
||
|
int n = points.length;
|
||
|
Point[] hull = new Point[n + 1];
|
||
|
int cnt = 0;
|
||
|
for (int i = 0; i < 2 * n; i++) {
|
||
|
int j = i < n ? i : 2 * n - 1 - i;
|
||
|
while (cnt >= 2 && isNotRightTurn(hull[cnt - 2], hull[cnt - 1], points[j]))
|
||
|
--cnt;
|
||
|
hull[cnt++] = points[j];
|
||
|
}
|
||
|
return Arrays.copyOf(hull, cnt - 1);
|
||
|
}
|
||
|
|
||
|
static boolean isNotRightTurn(Point a, Point b, Point c) {
|
||
|
long cross = (long) (a.x - b.x) * (c.y - b.y) - (long) (a.y - b.y) * (c.x - b.x);
|
||
|
long dot = (long) (a.x - b.x) * (c.x - b.x) + (long) (a.y - b.y) * (c.y - b.y);
|
||
|
return cross < 0 || cross == 0 && dot <= 0;
|
||
|
}
|
||
|
|
||
|
public static class Point {
|
||
|
public final int x, y;
|
||
|
|
||
|
public Point(int x, int y) {
|
||
|
this.x = x;
|
||
|
this.y = y;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static Point[] convexHull2(Point[] p) {
|
||
|
int n = p.length;
|
||
|
if (n <= 1)
|
||
|
return p;
|
||
|
Arrays.sort(p, (a, b) -> Integer.compare(a.x, b.x) != 0 ? Integer.compare(a.x, b.x) : Integer.compare(a.y, b.y));
|
||
|
Point[] h = new Point[n * 2];
|
||
|
int cnt = 0;
|
||
|
for (int i = 0; i < n; h[cnt++] = p[i++])
|
||
|
while (cnt > 1 && cross(h[cnt - 2], h[cnt - 1], p[i]) >= 0)
|
||
|
--cnt;
|
||
|
for (int i = n - 2, t = cnt; i >= 0; h[cnt++] = p[i--])
|
||
|
while (cnt > t && cross(h[cnt - 2], h[cnt - 1], p[i]) >= 0)
|
||
|
--cnt;
|
||
|
return Arrays.copyOf(h, cnt - 1 - (h[0].x == h[1].x && h[0].y == h[1].y ? 1 : 0));
|
||
|
}
|
||
|
|
||
|
static long cross(Point a, Point b, Point c) {
|
||
|
return (long) (b.x - a.x) * (c.y - a.y) - (long) (b.y - a.y) * (c.x - a.x);
|
||
|
}
|
||
|
|
||
|
// random test
|
||
|
public static void main(String[] args) {
|
||
|
Random rnd = new Random(1);
|
||
|
for (int step = 0; step < 100_000; step++) {
|
||
|
int n = rnd.nextInt(10) + 1;
|
||
|
Point[] points = new Point[n];
|
||
|
for (int i = 0; i < n; i++) {
|
||
|
int range = 10;
|
||
|
points[i] = new Point(rnd.nextInt(range) - range / 2, rnd.nextInt(range) - range / 2);
|
||
|
}
|
||
|
Point[] convexHull = convexHull(points);
|
||
|
Point[] convexHull2 = convexHull2(points);
|
||
|
for (int i = 0; i < Math.max(convexHull.length, convexHull2.length); i++)
|
||
|
if (convexHull[i].x != convexHull2[i].x || convexHull[i].y != convexHull2[i].y)
|
||
|
throw new RuntimeException();
|
||
|
for (int i = 0; i <= convexHull.length; i++) {
|
||
|
final Point[] hull;
|
||
|
if (i == 0) {
|
||
|
hull = convexHull;
|
||
|
} else {
|
||
|
List<Point> list = new ArrayList<>();
|
||
|
Collections.addAll(list, convexHull);
|
||
|
list.remove(i - 1);
|
||
|
hull = list.toArray(new Point[list.size()]);
|
||
|
}
|
||
|
boolean exterior = false;
|
||
|
for (Point point : points)
|
||
|
exterior |= pointInPolygon(point.x, point.y, hull) == -1;
|
||
|
if (exterior != (i > 0))
|
||
|
throw new RuntimeException();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int pointInPolygon(int qx, int qy, Point[] points) {
|
||
|
int n = points.length;
|
||
|
int cnt = 0;
|
||
|
for (int i = 0, j = n - 1; i < n; j = i++) {
|
||
|
if (points[i].y == qy && (points[i].x == qx || points[j].y == qy && (points[i].x <= qx || points[j].x <= qx) && (points[i].x >= qx || points[j].x >= qx)))
|
||
|
return 0; // boundary
|
||
|
if ((points[i].y > qy) != (points[j].y > qy)) {
|
||
|
long det = (long) (points[i].x - qx) * (points[j].y - qy) - (long) (points[j].x - qx) * (points[i].y - qy);
|
||
|
if (det == 0)
|
||
|
return 0; // boundary
|
||
|
if ((det > 0) != (points[j].y - points[i].y > 0))
|
||
|
++cnt;
|
||
|
}
|
||
|
}
|
||
|
return cnt % 2 == 0 ? -1 /* exterior */ : 1 /* interior */;
|
||
|
}
|
||
|
}
|