convexHull介绍
凸包(Convex Hull)问题表示如下: 在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。X的凸包可以用X内所有点(X1,…Xn)的凸组合来构造。 比较常用,也是实验中遇到的是二维平面上的凸包:给定平面上一个点集,凸包就是将最外围的点连接起来构成的凸多边形,它能包含点集中所有的点。可以想象成一条刚好包着所有点的橡皮圈。

注:这里对于边界的处理方法是舍弃位于边界(不包括顶点)上的点,如上图中的点9我们这里舍去,它不属于凸包,但它被凸包包围。
首先选取横坐标最小的一个点,若有多个这样的点,选纵坐标最小(上图的红点)。然后对其余点进行遍历,以红点为极点,y轴正半轴方向为极轴,按照顺时针方向,计算其余点的角度。选取角度最小的点,加入一个点集。若有多个点角度相同,则计算红点到这些点的距离,选取距离最小的点加入该点集(9、10号点选10号),重复上述步骤,直到即将加入凸包的点为红点时结束循环,此时得到的点集即为凸包。
// 计算转向角度,以y轴正向为极坐标顺时针转动,角度限制在0~360°之间
public static double calculateBearingToPoint(double currentBearing, int currentX, int currentY,
int targetX, int targetY) {
double X = targetX - currentX, Y = targetY - currentY , targetAngle ;
if( X == 0 ) {
if( Y >= 0 )
targetAngle = - currentBearing;
else
targetAngle = 180 - currentBearing;
}
else {
if( X > 0 ) {
if( Y >= 0 )
targetAngle = 90 - currentBearing - 180 * Math.atan( Y/X ) / Math.PI ;
else
targetAngle = 90 - currentBearing - ( 360 + 180 * Math.atan( Y/X ) / Math.PI );
}
else {
targetAngle = 90 - currentBearing - ( 180 + 180 * Math.atan( Y/X ) / Math.PI );
}
}
if( targetAngle - 360.0 >= 0 )
return targetAngle - 360.0;
else {
if( targetAngle + 360.0 <= 0 )
return targetAngle + 720.0;
else {
if ( targetAngle < 0 )
return targetAngle + 360.0;
else
return targetAngle;
}
}
}
convexHull// 主函数
public static Set<Point> convexHull(Set<Point> point) {
Iterator<Point> it = point.iterator() ;
HashSet<Point> HullHashSet = new HashSet<>();
if(point.isEmpty()) { //点集为空,返回空集
return HullHashSet ;
}
Point first_point = it.next(); //找到最左点
while(it.hasNext()) {
Point pr = it.next() ;
if (pr.x() < first_point.x() ) {
first_point = pr ;
}
}
HullHashSet.add(first_point); //凸包中加入最左点
point.remove(first_point); //剩余点集中删除最左点
Point this_point = first_point; //当前点
Point next_point; //下一个点
Point add_point = this_point; //加入凸包中的点
double min_angle; //最小转动角度
double next_angle; //当前转动角度
do {
it = point.iterator();
min_angle = 360;
while(it.hasNext()) {
next_point = it.next();
next_angle = calculateBearingToPoint(0 , (int)this_point.x() , (int)this_point.y() , (int)next_point.x() ,(int) next_point.y() );
if(next_angle < min_angle) { //选取角度最小的点
add_point = next_point;
min_angle = next_angle;
}
else if( next_angle == min_angle ) { //若角度相同,选取距离最远的点
if(Math.pow((this_point.x()-next_point.x()), 2) + Math.pow((this_point.y()-next_point.y()), 2) >
Math.pow((this_point.x()-add_point.x()), 2) + Math.pow((this_point.y()-add_point.y()), 2)) {
add_point = next_point ;
}
}
}
this_point = add_point;
HullHashSet.add(add_point); //加入凸包
point.remove(add_point); //从剩余点集中删除
point.add(first_point); //加入初始点,否则没有循环结束条件
}while(add_point != first_point);
point.addAll(HullHashSet);
return HullHashSet ;
}