package com.interview.algorithms.sort; import java.util.Arrays; import java.util.Comparator; import com.interview.basics.model.collection.stack.LinkedStack; import com.interview.basics.model.collection.stack.Stack; /** * The convex hull of a set of N points is the smallest perimeter fence enclosing the points. * There are 2 facts of the problem: * Fact1. Can traverse the convex hull by making only counterclockwise turns. * Fact2. The vertices of convex hull appear in increasing order of polar angle * with respect to point p with lowest y-coordinate. * * Solution is based on the previous 2 facts: * 1. Choose point p with smallest y-coordinate. * 2. Sort points by polar angle with p. * 3. Consider points in order; discard unless it create a counterclockwise turn. * * About counter clockwise: * by calculate (b.x-a.x) *(c.y-a.y)- (b.y-a.y)*(c.x-a.x), * if the result > 0, they are clockwise, * if result < 0, they ain't clockwise, * if result = 0, they are in a line * * @author stefaniezhao * */ class Point { public static final Comparator<Point> BY_Y_AXIS = new YAxisComparator(); public static final Comparator<Point> BY_ANGLE = new AngleComparator(); private double x; private double y; private double angle; public Point(double x, double y){ this.x = x; this.y = y; } public String toString(){ return "(" + x + ", " + y + ")"; } private static class YAxisComparator implements Comparator<Point>{ @Override public int compare(Point p1, Point p2) { if(p1.y == p2.y) return 0; else if (p1.y > p2.y) return 1; else return -1; } } private static class AngleComparator implements Comparator<Point>{ @Override public int compare(Point p1, Point p2) { if (p1.angle == p2.angle) return 0; else if(p1.angle > p2.angle) return 1; else return -1; } } public static int isLower(Point a, Point b){ if(a.y < b.y) return 1; else if (a.y > b.y) return -1; else return 0; } public static void sortByPolarAngle(Point[] points){ for(int i = 1; i < points.length; i++){ double dx = points[i].x - points[0].x; double dy = points[i].y - points[0].y; points[i].angle = Math.atan2(dy,dx); } Arrays.sort(points, 1, points.length, Point.BY_ANGLE); // //selection sorting // for(int i = 1; i < points.length; i++){ // int minIndex = i; // for(int j = i + 1; j < points.length; j ++){ // if(points[j].angle < points[minIndex].angle) // minIndex = j; // } // if(minIndex != i){ // Point temp = points[i]; // points[i] = points[minIndex]; // points[minIndex] = temp; // } // } } public static int counterclockwise(Point a, Point b, Point c){ double area2 = (b.x-a.x) *(c.y-a.y)- (b.y-a.y)*(c.x-a.x); if(area2 > 0) return 1; else if(area2 < 0) return -1; else return 0; } } public class ConvexHull { public static Point[] grahamScan(Point[] points){ Stack<Point> hull = new LinkedStack<Point>(); //1. sort point based on Y-coordinate to find p0. //getLowestY(points); Arrays.sort(points, Point.BY_Y_AXIS); //2. sort pints by polar angle with respect to p0. Point.sortByPolarAngle(points); // for(Point p : points){ // System.out.print(p.toString() + ", "); // } // System.out.println(); //find the edges hull.push(points[0]); hull.push(points[1]); for(int i = 2; i < points.length; i ++){ Point top = hull.pop(); //System.out.println(top.toString()); while(Point.counterclockwise(hull.peek(), top, points[i]) <= 0){ //System.out.println(top.toString()); top = hull.pop(); } hull.push(top); hull.push(points[i]); } int size = hull.size(); Point[] edges = new Point[size]; int index = 0; while(!hull.isEmpty()){ edges[index] = hull.pop(); index ++; } return edges; } public static void getLowestY(Point[] points){ int min = 0; for(int i = 1; i < points.length; i ++){ if(Point.isLower(points[i], points[min]) > 0){ min = i; } } //swap min and 0 Point temp = points[0]; points[0] = points[min]; points[min] = temp; } private static Point[] generateTestPoint() { Point[] testPoint = new Point[10]; //String pointStr = "0,0#1,0.5#1,1#2,1.5#0.5,1.5#1,2#0,2#0,1#-0.5,1"; //String pointStr = "7,1#0,4#8,8#3,6#5,3#6,5#4,0#9,9#2,7#1,2"; String pointStr = "8,4#9,2#4,9#1,5#0,6#7,7#5,8#3,1#6,3#2,0"; String[] points = pointStr.split("#"); for(int i = 0; i < points.length; i ++){ String[] coords = points[i].split(","); double x = Double.parseDouble(coords[0]); double y = Double.parseDouble(coords[1]); testPoint[i] = new Point(x, y); } return testPoint; } public static void main(String[] args){ Point[] testPoint = generateTestPoint(); for(Point p : testPoint){ System.out.print(p.toString() + ", "); } System.out.println(); Point[] edges = grahamScan(testPoint); for(Point p : edges){ System.out.print(p.toString() + ", "); } } }