-
Notifications
You must be signed in to change notification settings - Fork 20k
feat: Add ConvexHull
new algorithm with Junit tests
#5789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
c860d8a
feat: Add `ConvexHull` new algorithm with Junit tests
Hardvan e7fea8f
Update directory
Hardvan aa04b35
Add docs
Hardvan 77f739b
Merge remote-tracking branch 'origin/convex_hull_new_algo' into conve…
Hardvan 2113308
Fix
Hardvan c3a9bcb
Fix
Hardvan d5949e0
Fix
Hardvan 22af751
Fix
Hardvan ae561ca
Fix
Hardvan b3419c3
Merge branch 'master' into convex_hull_new_algo
Hardvan 53f7e54
Update directory
Hardvan bb4d519
Fix
Hardvan 0c88451
Merge remote-tracking branch 'origin/convex_hull_new_algo' into conve…
Hardvan 92638a6
Fix
Hardvan dbcce6b
Refactor to suggested changes
Hardvan 442a618
Update directory
Hardvan 5e86e9a
Fix
Hardvan d19db33
Merge remote-tracking branch 'origin/convex_hull_new_algo' into conve…
Hardvan 91df2bc
Fix
Hardvan 9d6e912
Merge branch 'master' into convex_hull_new_algo
alxkm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
229 changes: 229 additions & 0 deletions
229
src/main/java/com/thealgorithms/divideandconquer/ConvexHull.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
package com.thealgorithms.divideandconquer; | ||
Hardvan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.Comparator; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
import java.util.Stack; | ||
import java.util.TreeSet; | ||
|
||
/** | ||
* A class representing points on a 2D plane and 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 | ||
* 3. Graham Scan method | ||
* | ||
* @author Hardvan | ||
*/ | ||
public final class ConvexHull { | ||
private ConvexHull() { | ||
} | ||
|
||
public static class Point implements Comparable<Point> { | ||
Hardvan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
final int x; | ||
final int y; | ||
|
||
public Point(int x, int y) { | ||
this.x = x; | ||
this.y = y; | ||
} | ||
|
||
@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 boolean equals(Object obj) { | ||
if (!(obj instanceof Point)) { | ||
return false; | ||
} | ||
Point other = (Point) obj; | ||
return this.x == other.x && this.y == other.y; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(x, y); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return String.format("(%d, %d)", x, y); | ||
} | ||
|
||
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 | ||
} | ||
} | ||
} | ||
} | ||
|
||
private 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 static boolean checkPointOrientation(Point i, Point j, Point k) { | ||
int detK = 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) { | ||
TreeSet<Point> convexSet = new TreeSet<>(); | ||
Hardvan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 = 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 = 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); | ||
} | ||
} | ||
} | ||
|
||
public static List<Point> grahamScan(List<Point> points) { | ||
Hardvan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (points.size() <= 3) { | ||
return new ArrayList<>(points); | ||
} | ||
|
||
// Find the point with the lowest y-coordinate | ||
Point lowest = Collections.min(points); | ||
|
||
// Sort points by polar angle with respect to the lowest point | ||
points.sort(lowest.polarOrder()); | ||
|
||
Stack<Point> hull = new Stack<>(); | ||
hull.push(lowest); | ||
|
||
// Find the first point not equal to lowest | ||
int k1; | ||
for (k1 = 1; k1 < points.size(); k1++) { | ||
if (!lowest.equals(points.get(k1))) { | ||
break; | ||
} | ||
} | ||
if (k1 == points.size()) { | ||
return new ArrayList<>(hull); | ||
} | ||
|
||
// Find first point not collinear with lowest and points[k1] | ||
int k2; | ||
for (k2 = k1 + 1; k2 < points.size(); k2++) { | ||
if (orientation(lowest, points.get(k1), points.get(k2)) != 0) { | ||
break; | ||
} | ||
} | ||
hull.push(points.get(k2 - 1)); | ||
|
||
// Process remaining points | ||
for (int i = k2; i < points.size(); i++) { | ||
Point top = hull.pop(); | ||
while (orientation(hull.peek(), top, points.get(i)) <= 0) { | ||
top = hull.pop(); | ||
} | ||
hull.push(top); | ||
hull.push(points.get(i)); | ||
} | ||
|
||
return new ArrayList<>(hull); | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
src/test/java/com/thealgorithms/divideandconquer/ConvexHullTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package com.thealgorithms.divideandconquer; | ||
|
||
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<ConvexHull.Point> points = Arrays.asList(new ConvexHull.Point(0, 0), new ConvexHull.Point(1, 0), new ConvexHull.Point(10, 1)); | ||
List<ConvexHull.Point> expected = Arrays.asList(new ConvexHull.Point(0, 0), new ConvexHull.Point(1, 0), new ConvexHull.Point(10, 1)); | ||
assertEquals(expected, ConvexHull.convexHullBruteForce(points)); | ||
|
||
points = Arrays.asList(new ConvexHull.Point(0, 0), new ConvexHull.Point(1, 0), new ConvexHull.Point(10, 0)); | ||
expected = Arrays.asList(new ConvexHull.Point(0, 0), new ConvexHull.Point(10, 0)); | ||
assertEquals(expected, ConvexHull.convexHullBruteForce(points)); | ||
|
||
points = Arrays.asList( | ||
new ConvexHull.Point(0, 3), new ConvexHull.Point(2, 2), new ConvexHull.Point(1, 1), new ConvexHull.Point(2, 1), new ConvexHull.Point(3, 0), new ConvexHull.Point(0, 0), new ConvexHull.Point(3, 3), new ConvexHull.Point(2, -1), new ConvexHull.Point(2, -4), new ConvexHull.Point(1, -3)); | ||
expected = Arrays.asList(new ConvexHull.Point(2, -4), new ConvexHull.Point(1, -3), new ConvexHull.Point(0, 0), new ConvexHull.Point(3, 0), new ConvexHull.Point(0, 3), new ConvexHull.Point(3, 3)); | ||
assertEquals(expected, ConvexHull.convexHullBruteForce(points)); | ||
} | ||
|
||
@Test | ||
void testConvexHullRecursive() { | ||
List<ConvexHull.Point> points = Arrays.asList(new ConvexHull.Point(0, 0), new ConvexHull.Point(1, 0), new ConvexHull.Point(10, 1)); | ||
List<ConvexHull.Point> expected = Arrays.asList(new ConvexHull.Point(0, 0), new ConvexHull.Point(1, 0), new ConvexHull.Point(10, 1)); | ||
assertEquals(expected, ConvexHull.convexHullRecursive(points)); | ||
|
||
points = Arrays.asList(new ConvexHull.Point(0, 0), new ConvexHull.Point(1, 0), new ConvexHull.Point(10, 0)); | ||
expected = Arrays.asList(new ConvexHull.Point(0, 0), new ConvexHull.Point(10, 0)); | ||
assertEquals(expected, ConvexHull.convexHullRecursive(points)); | ||
|
||
points = Arrays.asList( | ||
new ConvexHull.Point(0, 3), new ConvexHull.Point(2, 2), new ConvexHull.Point(1, 1), new ConvexHull.Point(2, 1), new ConvexHull.Point(3, 0), new ConvexHull.Point(0, 0), new ConvexHull.Point(3, 3), new ConvexHull.Point(2, -1), new ConvexHull.Point(2, -4), new ConvexHull.Point(1, -3)); | ||
expected = Arrays.asList(new ConvexHull.Point(2, -4), new ConvexHull.Point(1, -3), new ConvexHull.Point(0, 0), new ConvexHull.Point(3, 0), new ConvexHull.Point(0, 3), new ConvexHull.Point(3, 3)); | ||
assertEquals(expected, ConvexHull.convexHullRecursive(points)); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.