-
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 7 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
228 changes: 228 additions & 0 deletions
228
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,228 @@ | ||
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.HashSet; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
|
||
/** | ||
* 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 | ||
* | ||
* @author Hardvan | ||
*/ | ||
class Point implements Comparable<Point> { | ||
double x; | ||
double y; | ||
|
||
/** | ||
* Constructor to initialize a point with x and y coordinates. | ||
* | ||
* @param x The x-coordinate of the point. | ||
* @param y The y-coordinate of the point. | ||
*/ | ||
Point(double x, double y) { | ||
this.x = x; | ||
this.y = y; | ||
} | ||
|
||
/** | ||
* Compares this point to another point based on x and y coordinates. | ||
* | ||
* @param other The other point to compare with. | ||
* @return A negative integer, zero, or a positive integer as this point is | ||
* less than, equal to, or greater than the specified point. | ||
*/ | ||
@Override | ||
public int compareTo(Point other) { | ||
if (this.x != other.x) { | ||
return Double.compare(this.x, other.x); | ||
} | ||
return Double.compare(this.y, other.y); | ||
} | ||
|
||
/** | ||
* Checks if this point is equal to another object based on x and y coordinates. | ||
* | ||
* @param obj The object to compare with. | ||
* @return true if the other object is a Point with the same coordinates; | ||
* false otherwise. | ||
*/ | ||
@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; | ||
} | ||
|
||
/** | ||
* Generates the hash code for the point based on its coordinates. | ||
* | ||
* @return The hash code of the point. | ||
*/ | ||
@Override | ||
public int hashCode() { | ||
return Objects.hash(x, y); | ||
} | ||
|
||
/** | ||
* Returns a string representation of the point in (x, y) format. | ||
* | ||
* @return A string representing the point. | ||
*/ | ||
@Override | ||
public String toString() { | ||
return String.format("(%.1f, %.1f)", x, y); | ||
} | ||
} | ||
|
||
/** | ||
* A class that provides two algorithms to find the convex hull of a set of points: | ||
* 1. Brute-force method | ||
* 2. Recursive (divide-and-conquer) method | ||
*/ | ||
public final class ConvexHull { | ||
private ConvexHull() { | ||
} | ||
|
||
/** | ||
* Computes the determinant of three points to determine their orientation. | ||
* | ||
* @param a The first point. | ||
* @param b The second point. | ||
* @param c The third point. | ||
* @return A positive value if points a, b, c are in counter-clockwise order; | ||
* negative if in clockwise order; and 0 if they are collinear. | ||
*/ | ||
private static double det(Point a, Point b, Point c) { | ||
return a.x * b.y + b.x * c.y + c.x * a.y - (a.y * b.x + b.y * c.x + c.y * a.x); | ||
} | ||
|
||
/** | ||
* Brute-force algorithm to find the convex hull of a set of points. | ||
* This algorithm checks every pair of points and determines if all other | ||
* points lie to one side of the line formed by these two points. | ||
* | ||
* @param points A list of points for which to compute the convex hull. | ||
* @return A sorted list of points forming the convex hull. | ||
*/ | ||
public static List<Point> convexHullBruteForce(List<Point> points) { | ||
Collections.sort(points); | ||
Hardvan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Set<Point> convexSet = new HashSet<>(); | ||
|
||
for (int i = 0; i < points.size() - 1; i++) { | ||
for (int j = i + 1; j < points.size(); j++) { | ||
boolean pointsLeftOfIJ = false; | ||
boolean pointsRightOfIJ = false; | ||
boolean ijPartOfConvexHull = true; | ||
|
||
for (int k = 0; k < points.size(); k++) { | ||
if (k != i && k != j) { | ||
double detK = det(points.get(i), points.get(j), points.get(k)); | ||
|
||
if (detK > 0) { | ||
pointsLeftOfIJ = true; | ||
} else if (detK < 0) { | ||
pointsRightOfIJ = true; | ||
} else if (points.get(k).compareTo(points.get(i)) < 0 || points.get(k).compareTo(points.get(j)) > 0) { | ||
Hardvan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ijPartOfConvexHull = false; | ||
break; | ||
} | ||
} | ||
|
||
if (pointsLeftOfIJ && pointsRightOfIJ) { | ||
ijPartOfConvexHull = false; | ||
break; | ||
} | ||
} | ||
|
||
if (ijPartOfConvexHull) { | ||
convexSet.add(points.get(i)); | ||
convexSet.add(points.get(j)); | ||
} | ||
} | ||
} | ||
|
||
List<Point> result = new ArrayList<>(convexSet); | ||
Collections.sort(result); | ||
return result; | ||
} | ||
|
||
/** | ||
* Recursive (divide-and-conquer) algorithm to find the convex hull of a set of points. | ||
* | ||
* @param points A list of points for which to compute the convex hull. | ||
* @return A sorted list of points forming the convex hull. | ||
*/ | ||
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++) { | ||
double det = det(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; | ||
} | ||
|
||
/** | ||
* Helper function to construct the convex hull recursively. | ||
* | ||
* @param points The list of candidate points for the hull. | ||
* @param left The left boundary point. | ||
* @param right The right boundary point. | ||
* @param convexSet The set to store points forming the convex hull. | ||
*/ | ||
private static void constructHull(List<Point> points, Point left, Point right, Set<Point> convexSet) { | ||
if (!points.isEmpty()) { | ||
Point extremePoint = null; | ||
double extremePointDistance = Double.NEGATIVE_INFINITY; | ||
List<Point> candidatePoints = new ArrayList<>(); | ||
|
||
for (Point p : points) { | ||
double det = det(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); | ||
} | ||
} | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
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,48 @@ | ||
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<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(-1, 1), new Point(-1, -1), new Point(0, 0), new Point(0.5, 0.5), new Point(1, -1), new Point(1, 1), new Point(-0.75, 1)); | ||
expected = Arrays.asList(new Point(-1, -1), new Point(-1, 1), new Point(1, -1), new Point(1, 1)); | ||
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(0, 0), new Point(0, 3), new Point(1, -3), new Point(2, -4), new Point(3, 0), 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(-1, 1), new Point(-1, -1), new Point(0, 0), new Point(0.5, 0.5), new Point(1, -1), new Point(1, 1), new Point(-0.75, 1)); | ||
expected = Arrays.asList(new Point(-1, -1), new Point(-1, 1), new Point(1, -1), new Point(1, 1)); | ||
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(0, 0), new Point(0, 3), new Point(1, -3), new Point(2, -4), new Point(3, 0), new 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.