mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-07-06 17:29:31 +08:00
feat: Add ConvexHull
new algorithm with Junit tests (#5789)
This commit is contained in:
@ -304,7 +304,9 @@
|
|||||||
* [WildcardMatching](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java)
|
* [WildcardMatching](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java)
|
||||||
* [WineProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java)
|
* [WineProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java)
|
||||||
* geometry
|
* geometry
|
||||||
|
* [ConvexHull](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/ConvexHull.java)
|
||||||
* [GrahamScan](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/GrahamScan.java)
|
* [GrahamScan](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/GrahamScan.java)
|
||||||
|
* [Point](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/Point.java)
|
||||||
* greedyalgorithms
|
* greedyalgorithms
|
||||||
* [ActivitySelection](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/ActivitySelection.java)
|
* [ActivitySelection](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/ActivitySelection.java)
|
||||||
* [BinaryAddition](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/BinaryAddition.java)
|
* [BinaryAddition](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/BinaryAddition.java)
|
||||||
@ -896,6 +898,7 @@
|
|||||||
* [WildcardMatchingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/WildcardMatchingTest.java)
|
* [WildcardMatchingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/WildcardMatchingTest.java)
|
||||||
* [WineProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java)
|
* [WineProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java)
|
||||||
* geometry
|
* geometry
|
||||||
|
* [ConvexHullTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java)
|
||||||
* [GrahamScanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java)
|
* [GrahamScanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java)
|
||||||
* greedyalgorithms
|
* greedyalgorithms
|
||||||
* [ActivitySelectionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/ActivitySelectionTest.java)
|
* [ActivitySelectionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/ActivitySelectionTest.java)
|
||||||
|
116
src/main/java/com/thealgorithms/geometry/ConvexHull.java
Normal file
116
src/main/java/com/thealgorithms/geometry/ConvexHull.java
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package com.thealgorithms.geometry;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Point> convexHullRecursive(List<Point> points) {
|
||||||
|
Collections.sort(points);
|
||||||
|
Set<Point> convexSet = new HashSet<>();
|
||||||
|
Point leftMostPoint = points.get(0);
|
||||||
|
Point rightMostPoint = points.get(points.size() - 1);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
List<Point> result = new ArrayList<>(convexSet);
|
||||||
|
Collections.sort(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void constructHull(List<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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@ package com.thealgorithms.geometry;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,93 +65,4 @@ public class GrahamScan {
|
|||||||
public Iterable<Point> hull() {
|
public Iterable<Point> hull() {
|
||||||
return new ArrayList<>(hull);
|
return new ArrayList<>(hull);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Point(int x, int y) implements Comparable<Point> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default constructor
|
|
||||||
* @param x x-coordinate
|
|
||||||
* @param y y-coordinate
|
|
||||||
*/
|
|
||||||
public Point {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the x-coordinate
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int x() {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the y-coordinate
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int y() {
|
|
||||||
return y;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines the orientation of the triplet (a, b, c).
|
|
||||||
*
|
|
||||||
* @param a The first point
|
|
||||||
* @param b The second point
|
|
||||||
* @param c The third point
|
|
||||||
* @return -1 if (a, b, c) is clockwise, 0 if collinear, +1 if counterclockwise
|
|
||||||
*/
|
|
||||||
public static int orientation(Point a, Point b, Point c) {
|
|
||||||
int val = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
|
||||||
return Integer.compare(val, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares this point with another point.
|
|
||||||
*
|
|
||||||
* @param p2 The point to compare to
|
|
||||||
* @return A positive integer if this point is greater, a negative integer if less, or 0 if equal
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int compareTo(Point p2) {
|
|
||||||
int cmpY = Integer.compare(this.y, p2.y);
|
|
||||||
return cmpY != 0 ? cmpY : Integer.compare(this.x, p2.x);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a comparator to sort points by their polar order relative to this point.
|
|
||||||
*
|
|
||||||
* @return A polar order comparator
|
|
||||||
*/
|
|
||||||
public Comparator<Point> polarOrder() {
|
|
||||||
return new PolarOrder();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class PolarOrder implements Comparator<Point> {
|
|
||||||
@Override
|
|
||||||
public int compare(Point p1, Point p2) {
|
|
||||||
int dx1 = p1.x - x;
|
|
||||||
int dy1 = p1.y - y;
|
|
||||||
int dx2 = p2.x - x;
|
|
||||||
int dy2 = p2.y - y;
|
|
||||||
|
|
||||||
if (dy1 >= 0 && dy2 < 0) {
|
|
||||||
return -1; // p1 above p2
|
|
||||||
} else if (dy2 >= 0 && dy1 < 0) {
|
|
||||||
return 1; // p1 below p2
|
|
||||||
} else if (dy1 == 0 && dy2 == 0) { // Collinear and horizontal
|
|
||||||
return Integer.compare(dx2, dx1);
|
|
||||||
} else {
|
|
||||||
return -orientation(Point.this, p1, p2); // Compare orientation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A string representation of this point in the format (x, y)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.format("(%d, %d)", x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
45
src/main/java/com/thealgorithms/geometry/Point.java
Normal file
45
src/main/java/com/thealgorithms/geometry/Point.java
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package com.thealgorithms.geometry;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
public record Point(int x, int y) implements Comparable<Point> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Point other) {
|
||||||
|
int cmpY = Integer.compare(this.y, other.y);
|
||||||
|
return cmpY != 0 ? cmpY : Integer.compare(this.x, other.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("(%d, %d)", x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Comparator<Point> polarOrder() {
|
||||||
|
return new PolarOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int orientation(Point a, Point b, Point c) {
|
||||||
|
return Integer.compare((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class PolarOrder implements Comparator<Point> {
|
||||||
|
@Override
|
||||||
|
public int compare(Point p1, Point p2) {
|
||||||
|
int dx1 = p1.x - x;
|
||||||
|
int dy1 = p1.y - y;
|
||||||
|
int dx2 = p2.x - x;
|
||||||
|
int dy2 = p2.y - y;
|
||||||
|
|
||||||
|
if (dy1 >= 0 && dy2 < 0) {
|
||||||
|
return -1; // p1 above p2
|
||||||
|
} else if (dy2 >= 0 && dy1 < 0) {
|
||||||
|
return 1; // p1 below p2
|
||||||
|
} else if (dy1 == 0 && dy2 == 0) { // Collinear and horizontal
|
||||||
|
return Integer.compare(dx2, dx1);
|
||||||
|
} else {
|
||||||
|
return -orientation(Point.this, p1, p2); // Compare orientation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
src/test/java/com/thealgorithms/geometry/ConvexHullTest.java
Normal file
40
src/test/java/com/thealgorithms/geometry/ConvexHullTest.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package com.thealgorithms.geometry;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class ConvexHullTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConvexHullBruteForce() {
|
||||||
|
List<Point> points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1));
|
||||||
|
List<Point> expected = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1));
|
||||||
|
assertEquals(expected, ConvexHull.convexHullBruteForce(points));
|
||||||
|
|
||||||
|
points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 0));
|
||||||
|
expected = Arrays.asList(new Point(0, 0), new Point(10, 0));
|
||||||
|
assertEquals(expected, ConvexHull.convexHullBruteForce(points));
|
||||||
|
|
||||||
|
points = Arrays.asList(new Point(0, 3), new Point(2, 2), new Point(1, 1), new Point(2, 1), new Point(3, 0), new Point(0, 0), new Point(3, 3), new Point(2, -1), new Point(2, -4), new Point(1, -3));
|
||||||
|
expected = Arrays.asList(new Point(2, -4), new Point(1, -3), new Point(0, 0), new Point(3, 0), new Point(0, 3), new Point(3, 3));
|
||||||
|
assertEquals(expected, ConvexHull.convexHullBruteForce(points));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConvexHullRecursive() {
|
||||||
|
List<Point> points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1));
|
||||||
|
List<Point> expected = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1));
|
||||||
|
assertEquals(expected, ConvexHull.convexHullRecursive(points));
|
||||||
|
|
||||||
|
points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 0));
|
||||||
|
expected = Arrays.asList(new Point(0, 0), new Point(10, 0));
|
||||||
|
assertEquals(expected, ConvexHull.convexHullRecursive(points));
|
||||||
|
|
||||||
|
points = Arrays.asList(new Point(0, 3), new Point(2, 2), new Point(1, 1), new Point(2, 1), new Point(3, 0), new Point(0, 0), new Point(3, 3), new Point(2, -1), new Point(2, -4), new Point(1, -3));
|
||||||
|
expected = Arrays.asList(new Point(2, -4), new Point(1, -3), new Point(0, 0), new Point(3, 0), new Point(0, 3), new Point(3, 3));
|
||||||
|
assertEquals(expected, ConvexHull.convexHullRecursive(points));
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
public class GrahamScanTest {
|
public class GrahamScanTest {
|
||||||
@Test
|
@Test
|
||||||
void testGrahamScan() {
|
void testGrahamScan() {
|
||||||
GrahamScan.Point[] points = {new GrahamScan.Point(0, 3), new GrahamScan.Point(1, 1), new GrahamScan.Point(2, 2), new GrahamScan.Point(4, 4), new GrahamScan.Point(0, 0), new GrahamScan.Point(1, 2), new GrahamScan.Point(3, 1), new GrahamScan.Point(3, 3)};
|
Point[] points = {new Point(0, 3), new Point(1, 1), new Point(2, 2), new Point(4, 4), new Point(0, 0), new Point(1, 2), new Point(3, 1), new Point(3, 3)};
|
||||||
String expectedResult = "[(0, 0), (3, 1), (4, 4), (0, 3)]";
|
String expectedResult = "[(0, 0), (3, 1), (4, 4), (0, 3)]";
|
||||||
|
|
||||||
GrahamScan graham = new GrahamScan(points);
|
GrahamScan graham = new GrahamScan(points);
|
||||||
|
Reference in New Issue
Block a user