分治法在求解凸包问题中的应用(JAVA)--快包算法

分治法在求解凸包问题中的应用(JAVA)

之前写过一篇蛮力法在求解凸包问题中的应用(JAVA)还算简单易懂,没有基础的读者最好先去阅读以下。
这里用分治法来求解凸包问题,由于这个算法和快速排序十分相似,因此又被称为“快包”。
在平面上有n>1个点构成的集合,定义为S,为简化思考,假定这些点按照x轴、y轴升序排列。有一个几何事实就是,最左边的一个点p1和最右边的一个点pn一定是集合的凸包顶点。我们连接p1与pn,这条直线将所有的点分成了两个子集合S1,S2。如果p1,p2,p3构成一个逆时针回路,我们称p3为与直线p1-p2的左侧;反之,称为p3为与直线p1-p2的右侧。另外S集合中位于p1-p2直线上的点,肯定不是凸包的顶点,直接忽略不考虑。
分治法在求解凸包问题中的应用(JAVA)--快包算法_第1张图片
S的凸包边界是有上下两条多角形链条组成的,“上”边界称为上包,上包由p1、S1(如果S1不为空)中的一些点、p2为端点组成,“下”边界称为下包,下包由p1、S2(如果S2不为空)中的一些点、p2为端点组成。上下端点用同样的方法构造而成,采用分治法思路更为清晰。下面以上包为例,分析所谓的快包算法:

分治法在求解凸包问题中的应用(JAVA)--快包算法_第2张图片
由于一些下标不好表示,这里截取了《算法设计与分析基础》中的文字讲解部分。
知道了原来,我们该如何实现呢?假设我们有三个点p1(x1, x1), p2(x2, y2),p3(x3, y3),这三点围成的三角形的面积可以用下面的公式计算:
分治法在求解凸包问题中的应用(JAVA)--快包算法_第3张图片
当且仅当p3位于直线p1-p2左侧时,该表达式符号为正。
和快速排序一样,该算法的最差时间复杂度为O(n^2),但是平均效率好很多!
下面的代码在精度上由于采用double类型,但是没有做判断上的处理,比如比较大小的时候,所以对于高精度的数据并不适用。
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

class Point {
    double x;
    double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }
}
public class Main {
    static Point[] point;
    static double[] s = new double[6];
    static Scanner in = new Scanner(System.in);

    public static void main(String[] args) {
        int n = in.nextInt();
        point = new Point[n];
//        for (int i = 0; i < n; i++) {
//            int a = in.nextInt();
//            int b = in.nextInt();
//            point[i] = new Point(a, b);
//        }
        point[0] = new Point(1,3);
        point[1] = new Point(2,1);
        point[2] = new Point(3,5);
        point[3] = new Point(4,4);
        point[4] = new Point(5,2);
        point[5] = new Point(3,2);
        Arrays.sort(point,0, n, new Comparator() {
            @Override
            public int compare(Point o1, Point o2) {
                if (o1.x - o2.x == 0) {
                    return (int) (o1.y - o2.y);
                }
                return (int) (o1.x - o2.x);
            }
        });
        System.out.println(point[0].x + "," + point[0].y);
        hull(1, n-1,point[0],point[0]);
    }

    private static void hull(int l,int r,Point p1,Point p2){
        int x=l;
        int i=l-1,j=r+1;
        /**
         * 找出距离直线p1-p2最远的点p3
         * */
        for (int k = l; k <= r; k++){
            if (s[x] - s[k] <= 0) {
                x=k;
            }
        }
        Point p3 = point[x];
        /**
         * p1-p3左侧的点
         * */
        for (int k = l; k <= r; k++) {

            s[++i] = cross(point[k], p1, p3);
            if (s[i] > 0) {
                Point temp = point[i];
                point[i] = point[k];
                point[k] = temp;
            } else {
                i--;
            }
        }
        /**
         * 直线p3-p2右侧的点
         * */
        for (int k=r;k>=l;k--) {
            s[--j]=cross(point[k], p3, p2);
            if (s[j] > 0) {
                Point temp = point[j];
                point[j] = point[k];
                point[k] = temp;
            } else {
                j++;
            }
        }
        /**
         * 分治,并中序输出
         * */
        if (l <= i) {
            hull(l, i, p1, p3);
        }
        System.out.println(p3.x + "," + p3.y);
        if (j <= r) {
            hull(j, r, p3, p2);
        }
    }
    private static double cross (Point a, Point b, Point c) {
        return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
    }
}




你可能感兴趣的:(数据结构与算法)