凸包问题
给定平面上一堆点集,输出位于凸包上的点
如下所示:输入如下这么多点集,输入P0,P1,P3,P10,P12
- 常见的算法有 n l o g 2 n nlog_2n nlog2n 的 G r a h a m S c a n Graham Scan GrahamScan 算法
- 本文讲解比较简单的 G i f t − W r a p p i n g Gift-Wrapping Gift−Wrapping 算法
算法描述
- 我们发现任意凸包上的点,你会发现以该点建立一个极角坐标系,该点连结其它所有点的极角中,该点逆时针方向的第一凸包点到该点极角最小,例如P0,到所有点的极角中P0P1极角最小。
- 算法中首先找到最左边的点,这个点必然在凸包上,然后计算该点连接点极角最小的,这里计算有技巧,算法中进行toright测试,直到找到到最右端的点,找到P1后,就可以从P1开始,接着顺次找到P2,又以P2为起点……
时间复杂度
- 因为每次的起点都是上次找到的凸包点,因此外层循环的复杂度为O(H),H为凸包上的点,内层循环每次都会全部遍历点,因此时间复杂度为 O(n) ,因此总的是间复杂度为 O(nH) ,在一般情况下 凸包上的点的期望为logn ,算法复杂度为 O(nlogn) ,极端情况下,如下所示,所有点都在类似圆弧上的话,外层循环也是n,因此会达到O(n^2)。
Java实现
- 作者写的程序中,比较极角的方法比较抽象
- 但是各位判断最左/右时可以自己通过几何推导一下
- 会有各种思路相同、实现不同的方法
public static Set<Point> convexHull(Set<Point> points) {
if (points.size() <= 3) return points;
Set<Point> convexHullPoints = new HashSet<Point>();
Point a = new Point(Double.MAX_VALUE, Double.MAX_VALUE);
for (Point i : points) {
if (i.x() < a.x() || (i.x() == a.x() && i.y() < a.y()))
a = i;
}
Point curPoint = a, minPoint = null, lastPoint = a;
double x1 = 0.0, y1 = -1.0;
do {
convexHullPoints.add(curPoint);
double minTheta = Double.MAX_VALUE, x2 = 0.0, y2 = 0.0;
for (Point i : points) {
if ((!convexHullPoints.contains(i) || i == a) && (i != lastPoint)) {
double x3 = i.x() - curPoint.x(), y3 = i.y() - curPoint.y();
double Theta = Math
.acos((x1 * x3 + y1 * y3) / Math.sqrt(x1 * x1 + y1 * y1) / Math.sqrt(x3 * x3 + y3 * y3));
// System.out.println(i.x() + " " + i.y() + " " + Theta);
if (Theta < minTheta || (Theta == minTheta && x3 * x3 + y3 * y3 > Math.pow(i.x() - minPoint.x(), 2)
+ Math.pow(i.y() - minPoint.y(), 2))) {
minPoint = i;
minTheta = Theta;
x2 = x3;
y2 = y3;
}
}
}
x1 = x2;
y1 = y2;
lastPoint = curPoint;
curPoint = minPoint;
// System.out.println(curPoint.x() + " " + curPoint.y());
} while (curPoint != a);
return convexHullPoints;
}