[软件构造] 凸包问题的简单算法
软件构造课作业
使用java有些许痛苦(bushi
一、定义
对于一个集合D,所有包含D的凸集之交称为D的凸包。
用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。
举个例子:
对于下图这个含有12个点的点集
这个点集对应的凸包,就是下图中点\(p_1\),\(p_3\),\(p_7\),\(p_9\),\(p_{11}\),\(p_{12}\)所构成的凸多边形
二、算法
Gift-Wrapping算法
首先很显然,对于一个点集,它最左,最右,最上,最下的点一定在凸包上。
该算法要先找到点集中最左的(\(x\)坐标最小的)那个点,将它加入凸包并设为\(x\)。
然后将点\(x\)作为极坐标原点建立极坐标系,将点\(x\)分别与其他点\(p\)连线形成向量\(xp\),找到其中位于最右,即在其他每一个向量右边的向量(可以通过叉积判断),将这个向量中的\(p_i\)加入凸包,并且更新\(x\)为\(p_i\),再重复上述的步骤......
直到发现新的那个\(x\)点与最初的那个\(x\)坐标最小的点相同。
借上一部分的例子模拟一下算法的过程。
我们首次先找到\(x\)坐标最小的点\(p_1\),将它加入凸包并设为\(x\)。然后将点\(x\)分别与其他点\(p\)连线形成向量\(xp\),发现向量\(xp_3\)在最右边,所以将\(p_3\)加入凸包。
接下来更新\(x\)为\(p_3\),然后再次将点\(x\)分别与其他点\(p\)连线形成向量\(xp\),发现向量\(xp_7\)在最右边,所以将\(p_7\)加入凸包。
然后再更新\(x\)为\(p_7\),......
以此类推,直到\(x\)为\(p_{12}\)时,找到最右的向量为\(xp_1\),发现\(p_1\)就是最初的那个点,于是算法结束。
不难分析出该算法的复杂度应当是\(O(n^2)\)
三、模板题
Surround the Trees
题意:树林里有n棵树,给出这些树的坐标,想要用一根绳子将这些树都围起来,问绳子的最小长度。
分析:凸包模板题,答案是凸包周长
要注意当n==2时,只需要求两树之间的距离。(挺奇怪的,原题说是surrounding来着,还以为是两倍呢
(以及用java写题是真的痛苦。。。还是c好 = =
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
public class Main {
public static class Point {
public double x;
public double y;
public Point(double x,double y) {
this.x = x;
this.y = y;
}
}
public static double getlength(Point A , Point B) {
return Math.sqrt(Math.pow(A.x - B.x , 2) + Math.pow(A.y - B.y , 2));
}
public static double cross(Point A , Point B) {
return A.x * B.y - A.y * B.x;
}
public static double convexHull(Set<Point> points) {
int n = points.size();
if (n < 2) return 0;
Set<Point> ans = new HashSet<Point>();
double cr;
Point a , fir ;
Point o = new Point(Double.MAX_VALUE, Double.MAX_VALUE) ;
//初始设置o点的x坐标为最大值
for (Point b : points) {
if (b.x < o.x || (b.x == o.x && b.y < o.y)) o = b;
}//找点集里x坐标最小的一个点
fir = o;//记录凸包中的第一个点
double len = 0;
do{
ans.add(o);
a = o;
for (Point b : points){
if (b == o) continue;
if (a == o) {
a = b;//给a赋初值
continue;
}//a点赋初值
Point oa = new Point(a.x - o.x , a.y - o.y);
Point ob = new Point(b.x - o.x , b.y - o.y);
//向量oa和ob
cr = cross(oa , ob);
//叉积判断向量ob在向量0a的哪一边,若为负数则ob在oa右边
if (cr < 0) a = b;
else if (cr == 0 && (Math.abs(a.x - o.x) > Math.abs(b.x - o.x) || Math.abs(a.y - o.y) > Math.abs(b.y - o.y))) a = b;
//当b点在oa连线上时,先考虑b点;否则先考虑a点
}//求出点集中在o点最左边的点a
len += getlength(o , a);
o = a;
}while (o != fir);
if (n == 2) len = len / 2;
return len;
}
public static void main(String []args) {
Scanner scanner=new Scanner(System.in);
int i , n;
while (scanner.hasNext()) {
n = scanner.nextInt();
if (n == 0) return;
Set<Point> set = new HashSet<Point>();
for (i = 0 ; i < n ; i++)
{
Point p = new Point(scanner.nextDouble(),scanner.nextDouble()) ;
set.add(p);
}
System.out.println(String.format("%.2f",convexHull(set)));
}
}
}