1.问题概述:
给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。
如下图所示,给定点的集合<0,1,2,3,4,5,6,7,8,9,10>,将最外层的点<0,1,3,6,7,10,9>连接起来,形成了一个凸多边形,这个多边形就是该点集合的凸包,所有的点都在凸包里面。
在Lab1中,关于凸包有如下细节:
1) 当凸包中的点少于3个的时候,所有的点都在凸包中;
2)当多个点在凸包顶点的连线的时候,选择距离最远的两个点。如上图中红色的点和9、10号点,这个时候,凸包集合中包含红的的0号点和10号点,但是9号点不在凸包集合中
所以凸包问题就是求解凸包顶点集合的问题。
2.Gift-Wrapping算法求解凸包问题
Gift-Wrapping算法又叫Jarvis步进法,采用一种“打包”的方法来计算点集合的凸包。算法的时间复杂度为O(hn),其中n是所有点的数量,h是凸包顶点的数量。
首先寻找一个点作为极点P0,要求这个点的x坐标最小,如果有多个这样的点,选择y坐标最小的点。这个点一定在凸包上。
接下来以P0作为极点,y轴作为极轴,寻找相对与P0的相对极角最小的一个点,如果有多个这样的点,选择距离P0最远的点加入到凸包集合。
假设选择了P1,加入了凸包集合,接下来选择P1作为新的极点,P0P1连线的方向作为新的极轴,继续寻找下一个点加入凸包集合。
以此类推,直到遇到P0后,说明形成和封闭图形,寻找凸包完毕。
实现的算法如下:
public static Set<Point> convexHull(Set<Point> points) {
Set<Point> convexHulls = new HashSet<Point>();//存放凸包顶点的集合
Set<Point>copyofpoints = new HashSet<Point>();//复制点的集合
/** 复制点到copyofpoints集合*/
for(Point temp:points)
{
copyofpoints.add(temp);
}
/**
* 当点的个数低于3个时,点全部在凸包点集合中
*/
if(copyofpoints.size()<3)
{
convexHulls=copyofpoints;
return convexHulls;
}
/**寻找第一个极点,位置是左下角*/
Point p0 = new Point(Double.MAX_VALUE, Double.MAX_VALUE);
for (Point item : copyofpoints) {
if (item.x() < p0.x() || (item.x() == p0.x() && item.y() < p0.y()))
p0 = item;
}
Point currentPoint = p0;
double currentAngle = 0;
convexHulls.add(currentPoint);//add p0
copyofpoints.remove(p0);
currentPoint = findNextPoint(copyofpoints,currentPoint,currentAngle);
convexHulls.add(currentPoint);
copyofpoints.remove(currentPoint);
copyofpoints.add(p0);
do
{
currentPoint = findNextPoint(copyofpoints,currentPoint,currentAngle);
convexHulls.add(currentPoint);
copyofpoints.remove(currentPoint);
}while(currentPoint!=p0);
return convexHulls;
//throw new RuntimeException("implement me!");
}
//寻找极角最小的下一个点
public static Point findNextPoint(Set<Point> copyofpoints,Point currentPoint,double currentAngle)
{
Point tempPoint = new Point(0,0);
double minAngle = 360.0;
double tempAngle = 0;
double maxDistance=0;
for(Point item : copyofpoints)
{
tempAngle=MycalculateBearingToPoint(currentAngle, currentPoint.x(), currentPoint.y(),
item.x(), item.y());
if(tempAngle<minAngle)
{
minAngle = tempAngle;
tempPoint = item;
maxDistance = Distance(tempPoint,currentPoint);
}
if(tempAngle==minAngle&&Distance(item,currentPoint)>maxDistance)
{
maxDistance= Distance(item,currentPoint);
minAngle = tempAngle;
tempPoint = item;
}
}
currentPoint = tempPoint;
currentAngle +=minAngle;
if(currentAngle>=360.0)
{
currentAngle-=360.0;
}
minAngle = 360.0;
return currentPoint;
}
//计算极角
public static double MycalculateBearingToPoint(double currentBearing, double currentX, double currentY,
double targetX, double targetY) {
double angle = 0;
double ax = targetX - currentX;
double ay = targetY - currentY;
double bx = 0;
double by = 1;
double cosangle = (ax * bx + ay * by) / (Math.sqrt(Math.pow(ax, 2.0) + Math.pow(ay, 2.0)));
double Targetangle = Math.toDegrees(Math.acos(cosangle));
if (ax < 0) {
Targetangle = 360.0 - Targetangle;
}
angle = Targetangle - currentBearing;
if (angle < 0) {
angle += 360.0;
}
return angle;
}
//计算两点间的距离
public static double Distance(Point currentPoint ,Point targetPoint) {
double distance= 0;
distance = Math.sqrt(Math.pow(currentPoint.x()-targetPoint.x(), 2.0)+Math.pow(currentPoint.y()-targetPoint.y(), 2.0));
return distance;
}
更多有关于凸包问题的算法参考凸包问题——概述 - 知乎 (zhihu.com)