1. 算法思路:
1. 选定y坐标最小(y坐标相同时取x最小)的点作为极点,这个点必在凸包上;
2. 将其余点按极角排序,在极角相同的情况下比较与极点的距离,离极点比较近的优先;
3. 用一个栈 pointStack
存储凸包上的点,先将按极角和极点排序最小的两个点入栈;
4. 按序扫描每个点,检查栈顶的两个元素与这个点构成的折线段是否“拐”向右侧;
( ! isLeftTurn(nextToTop, top, pi)
即 (crossProduct(nextToTop, top, pi) <= 0)
)
5. 如果满足,则弹出栈顶元素,并返回第 4. 步再次检查,直至不满足。将该点入栈,并对其余点不断执行此操作;
6. 最终栈中元素为凸包的顶点序列。
时间复杂度 :O(nlogn)
主要花在点集的排序上面,快排平均 O(nlogn)
2. 示例代码:
2.1 极角比较器
使用叉积比较两个点的极角
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| static class PointComparator implements Comparator<Point> { private Point p0; public PointComparator(Point p0) { this.p0 = p0; }
@Override public int compare(Point p1, Point p2) { double crossProduct = crossProduct(p0, p1, p2); if (crossProduct > 0) return -1; if (crossProduct < 0) return 1;
double d1 = p0.dist(p1); double d2 = p0.dist(p2); if (d1 < d2) return -1; if (d1 > d2) return 1; return 0; } }
|
-
crossProduct(p0, p1, p2) > 0
: P0P1×P0P2>0⇒P1<P2

-
crossProduct(p0, p1, p2) < 0
: P0P1×P0P2<0⇒P1>P2

-
crossProduct(p0, p1, p2) == 0
: P0,P1,P2 三点共线
- ∣P0P1∣<∣P0P2∣⇒P1<P2

- ∣P0P1∣>∣P0P2∣⇒P1>P2

2.2 Graham扫描算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public static Polygon Graham(ArrayList<Point> pointsList) {
ArrayList<Point> points = (ArrayList<Point>)pointsList.clone(); Stack<Point> pointStack = new Stack<Point>(); Point p0 = getLowestPoint(points); pointStack.push(p0); points.remove(p0); Collections.sort(points, new PointComparator(p0)); Iterator<Point> iter = points.iterator(); Point p1 = iter.next(); pointStack.push(p1); Point p2 = iter.next(); pointStack.push(p2); while (iter.hasNext()) { Point pi = iter.next(); Point top = pointStack.elementAt(pointStack.size() - 1); Point nextToTop = pointStack.elementAt(pointStack.size() - 2); while (! isLeftTurn(nextToTop, top, pi)) { pointStack.pop(); top = nextToTop; nextToTop = pointStack.elementAt(pointStack.size() - 2); } pointStack.push(pi); }
ArrayList<Point> polygon = new ArrayList<Point>(); while(! pointStack.empty()) { polygon.add(pointStack.pop()); } Collections.reverse(polygon); return new Polygon(polygon); }
|
-
找最低点 P0,进行排序

-
具体步骤
points |
pointStack |
|
P0,P1,P2,P3,P4,P5,P6,P7,P8,P9 |
|
|
P1,P2,P3,P4,P5,P6,P7,P8,P9 |
P0 |
|
P3,P4,P5,P6,P7,P8,P9 |
P0,P1,P2 |
|
P4,P5,P6,P7,P8,P9 |
P0,P1,P2,P3 |
P1P2 到 P1P3 左转,P3 压入 |
P5,P6,P7,P8,P9 |
P0,P1,P2,P3,P4 |
P2P3 到 P2P4 左转,P4 压入 |
P5,P6,P7,P8,P9 |
P0,P1,P2,P3 ( P4 ) |
P3P4 到 P3P5 右转,P4 弹出 |
P5,P6,P7,P8,P9 |
P0,P1,P2 ( P3 ) |
P2P3 到 P2P5 右转,P3 弹出 |
P6,P7,P8,P9 |
P0,P1,P2,P5 |
P1P2 到 P1P5 左转,P5 压入 |
P7,P8,P9 |
P0,P1,P2,P5,P6 |
P2P5 到 P2P6 左转,P6 压入 |
P8,P9 |
P0,P1,P2,P5,P6,P7 |
P5P6 到 P5P7 左转,P7 压入 |
P8,P9 |
P0,P1,P2,P5,P6 ( P7 ) |
P6P7 到 P6P8 右转,P7 弹出 |
P9 |
P0,P1,P2,P5,P6,P8 |
P5P6 到 P5P8 左转,P8 压入 |
|
P0,P1,P2,P5,P6,P8,P9 |
P6P8 到 P6P9 左转,P9 压入 |
2.3 其他方法
2.3.1 叉积
1 2 3 4 5 6 7 8 9 10
| private static double crossProduct(Point p0, Point p1, Point p2) { return (p1.getX() - p0.getX()) * (p2.getY() - p0.getY()) - (p2.getX() - p0.getX()) * (p1.getY() - p0.getY()); }
private static boolean isLeftTurn(Point p0, Point p1, Point p2) { return (crossProduct(p0, p1, p2) > 0); }
|
P0P1×P0P2

2.3.2 获得最低点
lowest-then-leftmost point(LTL)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| private static Point getLowestPoint(ArrayList<Point> points) { Point result = points.get(0);
Point candidate;
for (int i = 1; i < points.size(); i++) { candidate = points.get(i); if (candidate.getY() < result.getY() || candidate.getY() == result.getY() && candidate.getX() < result.getX()) { result = candidate; } } return result; }
|