[软件构造] 凸包问题的简单算法

软件构造课作业
使用java有些许痛苦(bushi

一、定义

对于一个集合D,所有包含D的凸集之交称为D的凸包。
用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。
举个例子:
对于下图这个含有12个点的点集
avatar
这个点集对应的凸包,就是下图中点\(p_1\),\(p_3\),\(p_7\),\(p_9\),\(p_{11}\),\(p_{12}\)所构成的凸多边形
avatar

二、算法

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\)加入凸包。
avatar
接下来更新\(x\)\(p_3\),然后再次将点\(x\)分别与其他点\(p\)连线形成向量\(xp\),发现向量\(xp_7\)在最右边,所以将\(p_7\)加入凸包。
avatar
然后再更新\(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)));
        }
    }
}
posted @ 2021-06-02 00:47  lsy_kk  阅读(158)  评论(0编辑  收藏  举报