凸包问题----利用Gift-Wrapping算法求解(Java实现)

凸包

首先我们解释一下什么叫做凸包。在算法导论第三版第33章中对于凸包的定义如下:点集Q的凸包是一个最小的凸多边形P,满足Q中的每个点都在P的边界上或者在P的内部。(关于凸多边形的准确定义大家可以自行百度。。)。我们假定Q中所有的点是独立的,并且Q至少包含3个不共线的点。直观地讲,可以把Q中的每个点都想象成是露在一块板外的铁钉,那么凸包就是包围了所有这些铁钉的一条拉紧了的橡皮绳所构成的形状。下图示出了一个点集及其凸包。
算法导论所给出的图
在算法导论这本书中将这种算法称作Jarvis步进法(Jarvis march),其运行时间为O(nh),其中h为凸包中的顶点数。当h为o(lgn)时,Jarvis步进法在渐近意义上比Graham扫描法更快。
Jarvis步进法运用了被称为“旋转扫除”的技术,根据每个顶点对一个参照顶点的极角的大小,依次进行处理。

算法描述

我们首先在平面上建立一个平面直角坐标系,使每个点都处于第一象限中(方便观察)。
算法中首先找到最左边的点,这个点一定是在凸包中的,然后遍历点集,计算与该点连接点中偏转角最小的,找到后放入点集,然后再次以这个新放入的点为计算点,再次计算偏转角中最小的点。。。依次循环下去,直到回到起始点。

Java程序实现

首先我们要先定义一个point类

public class Point {

    private final double x;
    private final double y;

    /**
     * Construct a point at the given coordinates.
     * @param x x-coordinate
     * @param y y-coordinate
     */
    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    /**
     * @return x-coordinate of the point
     */
    public double x() {
        return x;
    }

    /**
     * @return y-coordinate of the point
     */
    public double y() {
        return y;
    }
}

接下里是我们程序的实现代码,calculateBearingToPoint1函数是用来计算偏转角度的,第一个参数currentBearing保存了图保中上一个点与当前点相较于y轴的偏转角度。计算偏转角时方法并不唯一,我这里应用的是Java中自带的atan2函数,也可以用其他三角函数进行计算。
当点集中的点少于等于两个时,我们直接返回点集即可,因为此时所有的点都一定在凸包中。否则的话需要循环判断。(在这里因为老师给的凸包和算法导论中定义有一点点区别,我根据JUnit的测试对程序进行了一点修改)

    public static double calculateBearingToPoint1(double currentBearing, double currentX, double currentY,
    		double targetX, double targetY) {
    	double angle = Math.atan2(targetX - currentX, targetY - currentY) * 180 / Math.PI; 
    	double turnAngle = angle - currentBearing;
        if (turnAngle < 0)
            turnAngle += 360;
        return  turnAngle;

    }    
    public static Set<Point> convexHull(Set<Point> points) {                	
    	LinkedHashSet<Point> convexHull = new LinkedHashSet<Point>(); 
    	if(points.size() <= 2) {
        	return points;
        }
    	else {
    		Point xmin = new Point(Double.MAX_VALUE, Double.MAX_VALUE); 
    		for(Point item : points) {
    			if (item.x() < xmin.x() || (item.x() == xmin.x() && item.y() < xmin.y()))
    				xmin = item;
    		}
    		Point nowPoint = xmin, tempPoint = xmin;
    		double nowAngle = 0, minAngle = 360, tempAngle = 0;
    		double distance;
    		double maxdistance = -1;
    		do{
    			convexHull.add(nowPoint);
    			for(Point item : points) {
    				if ((!convexHull.contains(item) || item == xmin)  ) {
    					tempAngle = calculateBearingToPoint1(nowAngle, nowPoint.x(), nowPoint.y(), item.x(), item.y());
    					distance = (item.x() - nowPoint.x())*(item.x() - nowPoint.x()) + (item.y() - nowPoint.y())*(item.y() - nowPoint.y());
    					if(tempAngle < minAngle || ((tempAngle == minAngle) && (distance > maxdistance))) {
    						minAngle = tempAngle;
    						tempPoint = item;
    						maxdistance = distance;
    					}
    				}	
    			}
    			minAngle = 360;
    			nowAngle = minAngle;
    			nowPoint = tempPoint;
    		}while(nowPoint != xmin);
    		return convexHull;
    	}

    }

相较于这种实现方法,我们还可以采用左右链的策略,好处就是程序看着没这么抽象,不用再写一个计算角度的方法,消除了需要显式地计算角度所带来的代码量的增加。(毕竟,少一行代码少一点出错的风险)。左右链的方法大家可以自行查阅。

时间复杂度分析

对于凸包中的h个顶点,都需要找到具有做小偏转角的顶点。采用上述方法,每次偏转角的比较操作所需的时间为O(1)。如果每次比较操作所需时间为O(1),则可以在O(n)时间内计算出n个值中的最小值。因此,Jarvis步进法的运行时间为O(nh)。
上述内容为本人根据课程要求查阅资料后的个人见解,作复习反思、交流学习之用。如果错误,感谢大家指出。

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值