Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@
* divideandconquer
* [BinaryExponentiation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/BinaryExponentiation.java)
* [ClosestPair](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java)
* [ConvexHull](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/ConvexHull.java)
* [MedianOfTwoSortedArrays](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArrays.java)
* [SkylineAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/SkylineAlgorithm.java)
* [StrassenMatrixMultiplication](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/StrassenMatrixMultiplication.java)
Expand Down Expand Up @@ -832,6 +833,7 @@
* divideandconquer
* [BinaryExponentiationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/BinaryExponentiationTest.java)
* [ClosestPairTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java)
* [ConvexHullTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/ConvexHullTest.java)
* [MedianOfTwoSortedArraysTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArraysTest.java)
* [SkylineAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/SkylineAlgorithmTest.java)
* [StrassenMatrixMultiplicationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/StrassenMatrixMultiplicationTest.java)
Expand Down
228 changes: 228 additions & 0 deletions src/main/java/com/thealgorithms/divideandconquer/ConvexHull.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package com.thealgorithms.divideandconquer;

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);
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) {
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);
}
}
}
}
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));
}
}