GiftWrapping算法求最小凸包的简单实现

Posted lgxo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GiftWrapping算法求最小凸包的简单实现相关的知识,希望对你有一定的参考价值。


前言

本篇文章是基于哈工大软件构造的实验一写出的,源代码也只是TurtleSoup类中一个方法,虽然不能直接使用,但其思想还是有一定的参考价值。


问题简介

一组平面上的点,求一个包含所有点的最小的凸多边形,这就是凸包问题。最小凸包就是凸包中包含点数最少的那个凸包。


基本知识

凸包(Convex Hull)定义:
  在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。
  用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。
在这里插入图片描述


算法简介

Gift Wrapping思想是先找到凸包上一个点,然后从那个点开始按逆时针方向逐个找凸包上的点。(把所有点看成一根根柱子,先找到一个属于凸包的柱子,然后从这个柱子开始用绳子按逆时针方向把这些柱子都包住,这个绳子的每弯折一次,弯折处的柱子就归于凸包,知道绕完一圈,正好把凸包中的所有点都找出来)


算法简单实现过程

在实现函数之前,我们需要明确两点内容:

  1. 点集中点的个数要分情况讨论:当点的个数为0时,返回空集合;当点的个数小于等于3时,返回原点集即可;当点的个数大于3时采用GiftWrapping算法。(本人当时想到了三种情况,索性就直接按照三种情况写了,读者完全可以将前两种情况合并)
  2. 如果有多个点在凸包的一条边上,只需取这条边的两个端点入凸包即可,因为我们实现的是求最小凸包
    一定要先建立点集的一个副本,否则原点集被操作后,会产生意想不到的结果Set<Point> pointsCopy = new HashSet<Point>(points);
  • 根据算法首先找到一个在凸包上的点,容易得出,图集中最左下角的点一定属于凸包。实现代码片段如下:
//look for the point in the lower left corner(left) and the greatest x(right)
Iterator<Point> it = pointsCopy.iterator();
Point left = it.next();
Point right = left;
while(it.hasNext()) {
	Point tmp = it.next();
	//make sure that left is the point in the lower left corner
	if(tmp.x() < left.x() || (tmp.x() == left.x() && tmp.y() < left.y())) {
		left = tmp;
	}
	//make sure that point right has the largest abscissa
	if(tmp.x() > right.x()) {
		right = tmp;
	}
}
  • 在逐个找凸包上的点的阶段,为了在找点时方便(不用定义新方法或是采用复杂方法去求角度)将此阶段分为两部分——按逆时针先找凸包下半部分的点,然后再找上半部分的点:
    • 下半部分。
    //to find out the lower edge of convex hull
      do {
      	//reset degree
      	degree = -1;
      	//for each point vertex in pointsCopy whose abscissa greater than begin's
      	for(Point vertex: pointsCopy) {
      		//make sure point is next point belonging to set 
      		//it has the maximum angle to the right of point begin
      		if(!vertex.equals(begin) && vertex.x() >= begin.x()) {
      			double temp = Math.PI/2 - Math.atan((vertex.y() - begin.y())/(vertex.x() - begin.x()));
      			//for an edge, only the end point is included
      			if(temp > degree || (temp == degree &&  (vertex.x() > end.x() || vertex.y() > end.x()))) {
      				degree = temp;
      				end = vertex;
      			}
      		}
      	}
      	//update the HashSet
      	set.add(end);
      	pointsCopy.remove(end);
      	begin = end;
      }while(end.x() < right.x());
    
    • 上半部分。
    //to find out the upper edge of convex hull
      do {
      	//reset degree
      	degree = -1;
      	//for each point vertex in pointsCopy whose abscissa less than begin's
      	for(Point vertex: pointsCopy) {
      		//make sure point is next point belonging to set 
      		//(it has the maximum angle to the left of point begin)
      		if(vertex.x() <= begin.x()) {
      			//for an edge, only the end point is included
      			double temp = Math.PI/2 - Math.atan((begin.y() - vertex.y())/(begin.x() - vertex.x()));
      			if(temp > degree || (temp == degree &&  (vertex.x() < end.x() || vertex.y() < end.x()))) {
      				degree = temp;
      				end = vertex;
      			}
      		}
      	}
      	//update the HashSet
      	set.add(end);
      	pointsCopy.remove(end);
      	begin = end;
      }while(!end.equals(left));
    
  • 在找上下部分时,我们可以在判断哪个点的角度最大的同时判断哪个点最远(即同时找到凸包边上的一个端点)。
  • 求上下两部分的角度定义如下:
     下半部分:从正上顺时针旋转的角度,只需看刚刚找到的凸包上的点的右边的部分。
    在这里插入图片描述
     上半部分:从正下顺时针旋转的角度,只需看刚刚找到的凸包上的点的左边的部分。
    在这里插入图片描述

源代码

注释不是中文可能看着不太舒服(感觉反复切换输入法太麻烦了,所以源代码中就用英文注释了)

	 /**
     * Given a set of pointsCopy, compute the convex hull, the smallest convex set that contains all the pointsCopy 
     * in a set of input pointsCopy. The gift-wrapping algorithm is one simple approach to this problem, and 
     * there are other algorithms too.
     * 
     * @param pointsCopy a set of pointsCopy with xCoords and yCoords. It might be empty, contain only 1 point, two pointsCopy or more.
     * @return minimal subset of the input pointsCopy that form the vertices of the perimeter of the convex hull
     */
    public static Set<Point> convexHull(Set<Point> points) {
    	//initial the point set which stores convex hull elements
		Set<Point> set = new HashSet<Point>();
		set.clear();
		
		//when the number of elements in set points is 0
		if(points.isEmpty()) {
			;
		}
		
		//when the number of elements in set points is less than 3
		else if(points.size() <= 3) {
			set.addAll(points);
		}
		
		//when the number of elements in set points is greater than 3
		else {
			//create a copy of set points
	    	Set<Point> pointsCopy = new HashSet<Point>(points);
	    	
			//look for the point in the lower left corner(left) and the greatest x(right)
			Iterator<Point> it = pointsCopy.iterator();
			Point left = it.next();
			Point right = left;
			while(it.hasNext()) {
				Point tmp = it.next();
				//make sure that left is the point in the lower left corner
				if(tmp.x() < left.x() || (tmp.x() == left.x() && tmp.y() < left.y())) {
					left = tmp;
				}
				//make sure that point right has the largest abscissa
				if(tmp.x() > right.x()) {
					right = tmp;
				}
			}
			
			//point begin belongs to set, and point end will be next point belonging to set
			Point begin = left;
			Point end = right;
			//to record the greatest angle
			double degree;
			
			//to find out the lower edge of convex hull
			do {
				//reset degree
				degree = -1;
				//for each point vertex in pointsCopy whose abscissa greater than begin's
				for(Point vertex: pointsCopy) {
					//make sure point is next point belonging to set 
					//it has the maximum angle to the right of point begin
					if(!vertex.equals(begin) && vertex.x() >= begin.x()) {
						double temp = Math.PI/2 - Math.atan((vertex.y() - begin.y())/(vertex.x() - begin.x()));
						//for an edge, only the end point is included
						if(temp > degree || (temp == degree &&  (vertex.x() > end.x() || vertex.y() > end.x()))) {
							degree = temp;
							end = vertex;
						}
					}
				}
				//update the HashSet
				set.add(end);
				pointsCopy.remove(end);
				begin = end;
			}while(end.x() < right.x());
			
			//to find out the upper edge of convex hull
			do {
				//reset degree
				degree = -1;
				//for each point vertex in pointsCopy whose abscissa less than begin's
				for(Point vertex: pointsCopy) {
					//make sure point is next point belonging to set 
					//(it has the maximum angle to the left of point begin)
					if(vertex.x() <= begin.x()) {
						//for an edge, only the end point is included
						double temp = Math.PI/2 - Math.atan((begin.y() - vertex.y())/(begin.x() - vertex.x()));
						if(temp > degree || (temp == degree &&  (vertex.x() < end.x() || vertex.y() < end.x()))) {
							degree = temp;
							end = vertex;
						}
					}
				}
				//update the HashSet
				set.add(end);
				pointsCopy.remove(end);
				begin = end;
			}while(!end.equals(left));
		}
		
		return set;
    }

Point类

/* Copyright (c) 2007-2016 MIT 6.005 course staff, all rights reserved.
 * Redistribution of original or derived work requires permission of course staff.
 */
package P2.turtle;

/**
 * An immutable point in floating-point pixel space.
 */
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;
    }
}

结语

对于这个实现方法,如果有更好的建议,欢迎留言讨论。(本人英文水平一般般,所以注释读着可能不太通顺,还望包容😁)

以上是关于GiftWrapping算法求最小凸包的简单实现的主要内容,如果未能解决你的问题,请参考以下文章

凸包多边形最小外切矩形算法

BZOJ 1027 (凸包+floyd求最小环)

平面凸包Graham算法

Gym - 101635K:Blowing Candles (简单旋转卡壳,求凸包宽度)

poj1113--凸包(Andrew)

bzoj1185 [HNOI2007]最小矩形覆盖 旋转卡壳求凸包