GiftWrapping算法求最小凸包的简单实现
Posted lgxo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GiftWrapping算法求最小凸包的简单实现相关的知识,希望对你有一定的参考价值。
前言
本篇文章是基于哈工大软件构造的实验一写出的,源代码也只是TurtleSoup类中一个方法,虽然不能直接使用,但其思想还是有一定的参考价值。
问题简介
一组平面上的点,求一个包含所有点的最小的凸多边形,这就是凸包问题。最小凸包就是凸包中包含点数最少的那个凸包。
基本知识
凸包(Convex Hull)定义:
在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。
用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。
算法简介
Gift Wrapping思想是先找到凸包上一个点,然后从那个点开始按逆时针方向逐个找凸包上的点。(把所有点看成一根根柱子,先找到一个属于凸包的柱子,然后从这个柱子开始用绳子按逆时针方向把这些柱子都包住,这个绳子的每弯折一次,弯折处的柱子就归于凸包,知道绕完一圈,正好把凸包中的所有点都找出来)
算法简单实现过程
在实现函数之前,我们需要明确两点内容:
- 点集中点的个数要分情况讨论:当点的个数为0时,返回空集合;当点的个数小于等于3时,返回原点集即可;当点的个数大于3时采用GiftWrapping算法。(本人当时想到了三种情况,索性就直接按照三种情况写了,读者完全可以将前两种情况合并)
- 如果有多个点在凸包的一条边上,只需取这条边的两个端点入凸包即可,因为我们实现的是求最小凸包
一定要先建立点集的一个副本,否则原点集被操作后,会产生意想不到的结果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算法求最小凸包的简单实现的主要内容,如果未能解决你的问题,请参考以下文章