最小凸型集问题如下:现有一个点集Set<Point> S0,其中Point类是二维点的实现,拥有成员变量double x和double y。若集合S1定义为包围点集S0所有点的最小凸多边形则集合S=S0∩S1定义为集合S0的最小凸型集。
这个问题的一个解决方法是礼品包装盒算法,先附上Java代码:
public static Set<Point> convexHull(Set<Point> points) {
Point p[]=new Point[points.size()];
points.toArray(p);
ArrayList<Point> pList=new ArrayList<Point>();
for(int i=0;i<p.length;i++)
pList.add(p[i]);
Collections.sort(pList);
Set<Point> rs=new HashSet<Point>();
int tmp=0;
double currentDegree=0,curminDegree=360,degree=0;
Point p0=pList.get(0),p1=new Point(0,0);
do
{
for(int j=0;j<pList.size();j++)
{
if(tmp==j) continue;
p1=pList.get(j);
degree=calculateBearingToPoint(currentDegree,p0.x(),p0.y(),p1.x(),p1.y());
if(degree<curminDegree)
{
curminDegree=degree;
tmp=j;
}
}
currentDegree+=curminDegree;
curminDegree=360;
if(currentDegree>=360)
currentDegree-=360;
p0=pList.get(tmp);
rs.add(p0);
System.out.println(p0.x()+" "+p0.y());
}while(tmp!=0);
return rs;
}
public static double calculateBearingToPoint(double currentBearing, double currentX, double currentY, double targetX,
double targetY) {
double vectorX = targetX - currentX;
double vectorY = targetY - currentY;
double degree = 180 * Math.acos( vectorY / Math.sqrt(vectorX * vectorX + vectorY * vectorY)) / Math.PI;
if (vectorX < 0)
degree = 360 - degree;
double cbtp = degree - currentBearing;
if (cbtp < 0)
return 360 + degree;
else
return cbtp;
}
礼品包装盒算法基于如下定理:对于一个二维平面内的任意一点z,设集合S0的最小凸型集为S,z∈S的充要条件是该二维平面存在一组正交基(L1,L2)且若设X(z)和Y(z)定义为点z以(L1,L2)为基底对应的两个坐标值,对于平面内任意一点z0有X(z)≤X(z0)或Y(z)≤Y(z0)成立。(这一定理由反证法不难证明)
因此礼品包装盒算法通过先加入所有点中x值最小的点(Point类保存二维点所用的x,y本身便是以0,1和1,0两向量为正交基获得的坐标值,因此由上述定理x最小的点一定在最小凸点集内),再以这个点为基准点,从基准点从(0,1)方向开始顺时针旋转(如下图所示,图片所示是从p0以方向0,-1开始旋转,但由于我们选择的初始基准点是x最小的点,从0,-1方向和0,1方向开始不会有区别),找到剩余点中旋转角最小的点,加入该点并以该点为基准点重复这一过程,直至再次搜索到初始的基准点,此时便获得了集合S0的最小凸点集S。
上述代码中calculateBearingToPoint方法实现了从初始角度currentBearing,初始点(currentX,currentY)到目标点(targetX,targetY)的旋转角,因此只需按照算法反复调用这个方法即实现了最小凸点集问题的求解。