【HIT软件构造】凸包问题的求解思路与java实现

目录

1.凸包的定义

1.1凸多边形

2.2凸包

 2.计算转向角

3.凸包问题求解


本次实验过程中,任务二包含凸包算法的设计,对该任务的思考与解决记录如下。

1.凸包的定义

1.1凸多边形

        了解凸包之前,首先要先明确凸多边形的定义。什么是凸多边形?对于一个多边形,延长其任意一条边形成一条直线,它的其余各条边均在该条直线的同一边,那么该多边形就称为凸多边形。简单来说,凸多边形就是一个没有“凹陷”的多边形。

        如下图,第一个图形即为凸多边形,而第二个图形则并不是,它有着一个凹陷。

2.2凸包

        明确了凸多边形的定义后,就可以进一步说明凸包的定义。

        凸包在计算几何中的定义为:在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。X的凸包可以用X内所有点(X1,...Xn)的凸组合来构造。

        用更加通俗的话来讲,对于二维平面上的点集,最外层的点所连成的凸多边形就是该图的凸包,凸包包裹该点集的所有点。

 2.计算转向角

        为了方便凸包问题的求解,在这里首先解决一个前置问题。以某点作为出发点,面向某个方向,要前往指定坐标点,需要转过多少角度。为了方便计算,我们规定,以向上作为0角度,以顺时针作为正方向,只可顺时针转动。

        我们实现该功能的函数命名为calculateBearingToPoint。可知,要实现该函数需要五个参数,即当前面向的角度、当前坐标的X和Y、目标坐标的X和Y。

        以下图情况为例进行分析。原点为出发点,蓝点为目标点,红色的线为当前朝向,蓝色的线为目标朝向。绿色弧线标出的为当前角度,称为角1;黄色弧线为计算所得角,称为角2;紫色弧线标出的为需要转向的角度,称为角3。

        首先,我们通过Math.atan2()函数计算反三角函数,得到角2的大小。而角1通过函数的参数是已知的,那么我们就可以轻易的得到目标角3的值为:角3 = 90 - 角2 - 角1。

         之后,验证该公式对其他情况是否适用。将目标点选取到其他象限以及坐标轴上,带入该公式,发现依旧可以得到所需结果。只是在一些情况中会出现所得结果是负数的情况,因此要通过加360的方式对该情况进行处理。

        则该函数完整代码如下:

public static double calculateBearingToPoint(double currentBearing, int currentX, int currentY,
                                                 int targetX, int targetY) {
        //运用三角函数计算夹角
        double angle = Math.atan2(targetY - currentY, targetX - currentX) * 180.0 / Math.PI;
        if (angle < 0)//处理小于零的情况
            angle += 360.0;
        //计算得到与y轴的夹角,并减去当前角
        double bearing = (360 - angle + 90 >= 360 ? 90 - angle : 360 - angle + 90) - currentBearing;
        //返回一个正值
        return bearing < 0 ? 360.0 + bearing : bearing;
    }

3.凸包问题求解

        首先,根据点集中点的个数,我们将其分为两种情况。当点集中的点少于等于两个时,凸包由点集中的所有点组成,直接将两点返回。当多余两个点时,再进行筛选。这里需要注意,仅含三个点时也不可直接返回,因为可能出现三个点在一条直线的情况,此时只包含两端的两点。

        如何进行查找呢?先不考虑计算机的语言,先用我们的思想进行考虑。当从某点出发,我们要连接下一个点,那这个下一个点必然是剩下的点中“最靠外的点”。那这个“最靠外的点”如何通过计算机的语言来表示?很显然。那就是转过角度最小的点。明确了这个,那我们的思路就基本清晰了。

        现在,选定一个起始点就可以开始进行比较筛选了。那这个初始点又该如何选取?我们选取的这个点要保证包含在凸包内才可以,即该点要在最外面。结合刚才我们编写计算转角函数时将向上作为0角度,顺时针作为正方向,我们不妨选择最左下的角作为出发点 ,便于我们的思考。因此,在程序开始时,我们要通过循环遍历整个点集,查找横纵坐标最小的点。找到该点后记录下来,并加入到凸包的点集中。

for (Point temp : points){
            if (temp.x() < first.x() || (temp.x() == first.x() && temp.y() < first.y()))
                first = temp;
        }

        之后就可以开始对点集进行遍历,通过前一个问题中我们编写的函数,计算每一步的偏角,找到偏角最小的点依次加入集合。

if ((!convexHullPoints.contains(temp) || (temp == first && curpoint != first)) && corner < mincorner) {
                    mincorner = corner;
                    nextpoint = temp;
                    distance = Math.sqrt((temp.y() - curpoint.y()) * (temp.y() - curpoint.y()) + (temp.x() - curpoint.x()) * (temp.x() - curpoint.x()));
                }

        同时我们应该考虑到,除了这种情况之外,可能会出现两个点转向角相同的情况,对于这种情况我们要选择距离更远的点。

if ((!convexHullPoints.contains(temp) || (temp == first && curpoint != first)) && corner == mincorner) {
                    if (Math.sqrt((temp.y() - curpoint.y()) * (temp.y() - curpoint.y()) + (temp.x() - curpoint.x()) * (temp.x() - curpoint.x())) > distance) {
                        nextpoint = temp;
                        distance = Math.sqrt((temp.y() - curpoint.y()) * (temp.y() - curpoint.y()) + (temp.x() - curpoint.x()) * (temp.x() - curpoint.x()));
                    }
                }

        当我们筛选下一个点又返回到起始点时,说明已经形成了一个闭环,结果集合中所储存的即为连接成凸包的点集,将结果点集返回即可。

        完整代码如下:

public static Set<Point> convexHull(Set<Point> points) {
        if (points.size() < 3) //小于三个点的情况,直接返回
            return points;
        Set<Point> convexHullPoints = new HashSet<Point>();
        Point first = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
        for (Point temp : points){ //找到最左下角的点
            if (temp.x() < first.x() || (temp.x() == first.x() && temp.y() < first.y()))
                first = temp;
        }
        convexHullPoints.add(first); //加入到结果集合
        Point curpoint = first, nextpoint = null;
        double angle = 0, mincorner = 360, corner = 0, distance = 0;
        while (nextpoint != first) { //没有回到起点,即没有结束
            for (Point temp : points) { //遍历所有点
                //计算当前遍历的点与所处点的转角角度
                corner = calculateBearingToPoint(angle, (int) curpoint.x(), (int) curpoint.y(), (int) temp.x(), (int) temp.y());
                //角度最小的情况
                if ((!convexHullPoints.contains(temp) || (temp == first && curpoint != first)) && corner < mincorner) {
                    mincorner = corner;
                    nextpoint = temp;
                    distance = Math.sqrt((temp.y() - curpoint.y()) * (temp.y() - curpoint.y()) + (temp.x() - curpoint.x()) * (temp.x() - curpoint.x()));
                }
                //角度相等距离最远的情况
                if ((!convexHullPoints.contains(temp) || (temp == first && curpoint != first)) && corner == mincorner) {
                    if (Math.sqrt((temp.y() - curpoint.y()) * (temp.y() - curpoint.y()) + (temp.x() - curpoint.x()) * (temp.x() - curpoint.x())) > distance) {
                        nextpoint = temp;
                        distance = Math.sqrt((temp.y() - curpoint.y()) * (temp.y() - curpoint.y()) + (temp.x() - curpoint.x()) * (temp.x() - curpoint.x()));
                    }
                }
            }
            convexHullPoints.add(nextpoint); //加入结果集合
            curpoint = nextpoint;
            angle += mincorner; //当前角度
            mincorner = 360;
        }
        return convexHullPoints;
    }

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

麻了巴卡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值