前言
在软件构造lab1中,要求用Java语言编写代码,完成凸包(Convex Hull)问题的解决,算法有很多,本文仅介绍实验指导书建议采用的Gift-Wrapping算法。
一、凸包问题是什么?
凸包(Convex Hull)是一个计算几何(图形学)中的概念。在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。X的凸包可以用X内所有点(X1,…Xn)的凸组合来构造.
在二维欧几里得空间中,凸包可想象为一条刚好包著所有点的橡皮圈。
用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它是能包含点集中所有的点的最小的凸多边形。
二、Gift-Wrapping算法
1.简述思路
顾名思义,所有的点就是“礼物”,找凸包的过程就是把他们包装起来的过程,因此本算法从一个起始点开始,沿某一初始方向,将所有点均包裹起来,得到的路径经过的点即所求结果。
2.具体实现
难点在于如何从一个点,前进到下一个点,实现逐步的包装操作。
由于该问题出现在实验的一步中,而实验的前一步刚刚完成了一个方法,实现了计算已知起点与起始方向,计算转向终点的方向转动的角度大小,该方法实现代码如下:
public static double expandedCalculateBearingToPoint(double currentBearing, double currentX, double currentY, double targetX, double targetY) {
double x = targetX - currentX;
double y = targetY - currentY;
double angle = Math.toDegrees(Math.atan2(y, x));
angle = 90 - angle - currentBearing;
angle = angle >= 0 ? angle : (angle + 360);
return angle;
}
函数名中的expended是因为原方法的两点横纵坐标是int,本方法拓展到double类型。
然后调用该方法完成凸包问题的求解,该方法实现代码如下:
public static Set<Point> convexHull(Set<Point> points) {
if (points.size() <= 3)
return points;
HashSet<Point> result = new HashSet<Point>();
Point start = points.iterator().next();
for (Point p : points) {
if (p.x() < start.x() || (p.x() == start.x() && p.y() < start.y()))
start = p;
}
result.add(start);
double currentBearing = 0; // 初始化方向为向上
double angle, min;
Point current = start;
Point target = points.iterator().next();
if (target == start)
target = points.iterator().next();
while (target != start) {
min = 360;
for (Point p : points) {
if (p == current)
continue;
angle = expandedCalculateBearingToPoint(currentBearing, current.x(), current.y(), p.x(), p.y());
if ((angle < min) || ((angle == min) && (calculateDistance(current.x(), current.y(), p.x(), p.y()) > calculateDistance(current.x(), current.y(), target.x(), target.y())))) {
target = p;
min = angle;
}
}
result.add(target);
current = target;
currentBearing = min;
}
return result;
}
总结
先确定最左上角的点(也可以是其他的某个一定在凸包边界上的点)为初始点,确定y轴正方向(也可以是其他方向,保证该方向所在直线将图分成的两侧,一侧不含点)为初始朝向;选取下一个点的标准:利用Problem6中设计的函数的拓展版(点的坐标拓展到double)计算从上一个点到下一个点的角度最小,多者最小选距离(自己构造了一个函数用于求两点间距离)最大者为下一个点,直到找了一圈后回到初始点。