凸壳问题——Graham扫描法Java实践

凸壳问题:

       在平面上,给定n个点组成的集合,其凸壳就是包含这些点的最小凸多边形。这个凸多边形的任何一条边所在的直线都会把凸多边形全部划在同一个半平面内。举个简单的例子,在木板是随机钉入多个钉子,用个橡皮筋将所有钉子包起来,这即为包凸问题,显而易见橡皮筋最后所形成的形状即为凸壳问题的解。

Graham扫描法(格雷厄姆算法):

       格雷厄姆算法基于分治策略,通过递归的方式将问题分解为更小的子问题,最终将所有子问题的解合并为原始问题的解。在实际应用中先找到一个在包凸上的点(极点),逆时针方向找到包凸上的所有点。格雷厄姆的两个关键知识点:

角排量:对所有点按照该点与极点形成的线段与x轴正方向所形成的夹角升序排序

叉积:向量积,OAOB向量积小于0时,OA位于OB左侧

代码实现:

//凸包算法(Graham算法)
    public static List<Point> GrahamScan(List<Point> points){
        if (points.size() < 3){
            return points;
        }
        //对点坐标冒泡排序 先y再x,找到左下角的点
        Collections.sort(points, new Comparator<Point>() {
            @Override
            public int compare(Point o1, Point o2) {
                if (o1.y != o2.y) {
                    return Double.compare(o1.y, o2.y);
                } else {
                    return Double.compare(o1.x,o2.x);
                }
            }
        });
        Point point = points.get(0);
        points.remove(0);
        //极点与任意点连线,与x轴正方向形成夹角从小到大排序,角度相同近小远大
        Collections.sort(points, new Comparator<Point>() {
            @Override
            public int compare(Point o1, Point o2) {
                double angle_X1 = Math.atan2(o1.y - point.y,o1.x - point.x);
                double angle_X2 = Math.atan2(o2.y - point.y,o2.x - point.x);
                if (angle_X1 != angle_X2){
                    return Double.compare(angle_X1,angle_X2);
                }else {
                    return Double.compare(o1.y,o2.y);
                }
            }
        });
        points.add(0,point);
        //创建栈(栈顶操作,先进后出),放入坐标
        Deque<Point> stack = new ArrayDeque<>();//双端队列
        stack.add(points.get(1));
        stack.add(points.get(0));//top

        //实现格雷厄姆算法,执行格雷厄姆扫描:从极点出发,按照标号连线
        for (int i = 2; i < points.size();i++){
            Point p = points.get(i);
            while (stack.size() > 1){
                Point top = stack.pop();//获取头节点,不放回
                Point prev = stack.peek();//获取第二个节点,仅查看
                Double R = point_Place(prev,top,p);
                //p在向量左侧或三点共线且p点在两点之间
                if (R > 0 ||(R == 0 && top.x > p.x)){
                    stack.push(top);//放回栈顶
                    break;
                }
            }
            stack.push(p);
        }
        //从栈中提取凸包的顶点
        List<Point> hull = new ArrayList<>();
        while (!stack.isEmpty()) {
            hull.add(stack.pollLast());
        }
       //Collections.reverse(hull);
        return hull;
    }
    //辅助算法:判断点C与向量AB的位置
    public static double point_Place(Point A,Point B,Point C){
        return A.crossProduct(B,C);
    }

    class Point {
    //坐标类
    double x,y;
    public Point(double X,double Y){
        this.x = X;
        this.y = Y;
    }


    //计算向量叉积,判断点位置  向量A*向量B >0 A在B右
    public double crossProduct(Point A,Point B){
        return (A.x - this.x)*(B.y - this.y) - (B.x - this.x)*(A.y - this.y);
    }

    @Override
    public String toString() {
        return "(" + x +
                ", " + y +
                ')';
    }

    public double getX() {
        return x;
    }

    public void setX(double x) {
        this.x = x;
    }

    public double getY() {
        return y;
    }

    public void setY(double y) {
        this.y = y;
    }
}

注意:

       Math.atan2()方法返回值,在一二象限为正,而在该实现方法中设定的极点的y坐标是最小值,所以以极点为原点排序时,所有点均在一二象限,不考虑为负的情况,若改为其他排序方式需考虑这个问题。 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

菜鸟0917

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

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

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

打赏作者

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

抵扣说明:

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

余额充值