二维坐标点中查找最大凸多边形Java实现

参考如下文献中的Ruby代码实现的:

http://www.jianshu.com/p/a297324f4906


import java.util.LinkedList;
import java.util.List;

/**
 * Created by xxx on 2017/1/17.
 * <p>
 * 给定一系列坐标点,从中找出最大凸多边形
 * <p>
 * 可以判断,叉乘结果的数组中:
 * <p>
 * 1. 如果元素全部是负数,那么这个单点肯定在已知多边形的内部;这个单点应该直接抛弃;
 * 2. 如果只有一个零,其它都是负数,那么这个单点一定在对应的边上;这个单点也应该直接抛弃;
 * 3. 如果有两个零,那这个点一定和原来的某个顶点重合;应该抛弃这个单点;
 * 4. 如果有一个或者多个连续的正数或者0,那么这个单点一定在这几条连续边的外面,例如上面的EA ^ EB , EB ^EC是0 和 正数,那么对应的连续顶点A-B-C,应该保留起始点A和结束点C,中间的点B则应该删除,然后将单点E插入到A和C中间;从而形成新的凸多边形顶点列表AECD;
 * 5. 如果只有一个单独的正数叉乘结果,那就不需要删除任何原来的顶点,而仅需要将新的单点插入即可。
 * 6. 不可能出现多段分开的正数或零序列,比如[-1, -1, 0, 1, 1, 1, 0, -1] 是可能的,但是如下的[-1, -1, 0, 1, -1, 0, 1, -1]叉乘结果序列是绝不可能出现的。
 */
public class MaxConvexPolygon {

    private final double EPS = Math.pow(10, -6);

    private double[] coordinates;   //存放坐标点,e.g.[x0,y0,x1,y1,...]
    private List<Integer> ptsIdx = new LinkedList<>();

    public MaxConvexPolygon(double[] coordinates) {
        this.coordinates = coordinates;
        if (coordinates.length % 2 != 0)
            throw new IllegalArgumentException("坐标点coordinates长度必须是偶数");
        for (int i = 0; i < coordinates.length / 2; i++) ptsIdx.add(i);
    }

    public double[] run() {
        List<Integer> res = getMaxPolygon(ptsIdx);
        double[] validPts = new double[res.size() * 2];
        for (int i = 0; i < res.size(); i++) {
            validPts[i * 2] = coordinates[res.get(i) * 2];
            validPts[i * 2 + 1] = coordinates[res.get(i) * 2 + 1];
        }
        return validPts;
    }

    private List<Integer> getMaxPolygon(List<Integer> ptsIdx) {
        if (ptsIdx.size() == 3) {
            int[] idx = new int[]{ptsIdx.remove(0), ptsIdx.remove(0), ptsIdx.remove(0)};
            //生成两个向量
            double[] vectors = makeVector(idx[0], new Integer[]{idx[1], idx[2]}); // 0 -> 1, 0 -> 2
            //求交叉积
            int res = cross_mul_sign(vectors[0], vectors[1], vectors[2], vectors[3]);
            switch (res) {
                case -1: //保证点是顺时针顺序
                    ptsIdx.add(idx[0]);
                    ptsIdx.add(idx[1]);
                    ptsIdx.add(idx[2]);
                    break;
                case 1:
                    ptsIdx.add(idx[0]);
                    ptsIdx.add(idx[2]);
                    ptsIdx.add(idx[1]);
                    break;
                case 0: //在同一条直线上,去掉中间的点
                    int minIdx = 0, maxIdx = 0;
                    if (coordinates[idx[0] * 2] == coordinates[idx[1] * 2]) { //x 相同
                        for (int i = 1; i < idx.length; i++) {
                            if (coordinates[idx[minIdx] * 2 + 1] > coordinates[idx[i] * 2 + 1]) minIdx = i;
                            if (coordinates[idx[maxIdx] * 2 + 1] < coordinates[idx[i] * 2 + 1]) maxIdx = i;
                        }
                    } else {
                        for (int i = 1; i < idx.length; i++) {
                            if (coordinates[idx[minIdx] * 2] > coordinates[idx[i] * 2]) minIdx = i;
                            if (coordinates[idx[maxIdx] * 2] < coordinates[idx[i] * 2]) maxIdx = i;
                        }
                    }
                    ptsIdx.add(idx[minIdx]);
                    ptsIdx.add(idx[maxIdx]);
                    break;
            }
            return ptsIdx;
        } else {
            int firstIdx = ptsIdx.remove(0);
            List<Integer> poly_point_idx = getMaxPolygon(ptsIdx);
            double[] vectors = makeVector(firstIdx, poly_point_idx.toArray(new Integer[poly_point_idx.size()]));
            int[] cm_results = cross_mul_sign(vectors);

            int first_01 = -1, last_01 = -1; //寻找连续0或者1序列的起点和终点
            for (int i = 0; i < cm_results.length; i++) { //把数组看成一个环?0:
                if (first_01 == -1 && cm_results[i] >= 0 && cm_results[i == 0 ? cm_results.length - 1 : i - 1] < 0)
                    first_01 = i;
                if (last_01 == -1 && cm_results[i] >= 0 && (cm_results[0] < 0 || cm_results[i + 1 == cm_results.length ? 0 : i + 1] < 0))
                    last_01 = i;
                if (last_01 > -1 && first_01 > -1) break;
            }

            //元素全部是负数,那么这个单点肯定在已知多边形的内部;这个单点应该直接抛弃;
            if (first_01 == -1 || last_01 == -1)
                return poly_point_idx;
            //如果只有一个零,其它都是负数,那么这个单点一定在对应的边上;这个单点也应该直接抛弃;
            if (first_01 == last_01 && cm_results[first_01] == 0)
                return poly_point_idx;

            // 如果有一个或者多个连续的正数或者0,那么这个单点一定在这几条连续边的外面
            // 如果只有一个单独的正数叉乘结果,那就不需要删除任何原来的顶点,而仅需要将新的单点插入即可。
            int cnt_should_delete = last_01 - first_01;
            if (cnt_should_delete < 0)
                cnt_should_delete = poly_point_idx.size() - first_01 + last_01;
            //将firstIdx插入到poly_point_idx的first_01后面
            poly_point_idx.add(first_01 + 1, firstIdx);

            for (int i = 0; i < cnt_should_delete; i++) {
                int idx2del = first_01 + 2; //remove the points between first01 and last01
                if (idx2del >= poly_point_idx.size()) idx2del = 0;
                poly_point_idx.remove(idx2del);
            }

            return poly_point_idx;
        }
    }

    public static void main(String[] args) {

        double[] test = new double[]{3, 0, 0, 0, 3, 3, 0, 3, 3, 4};

        MaxConvexPolygon mcp = new MaxConvexPolygon(test);

        double[] res = mcp.run();
        System.out.println();
        for (double v : res) System.out.print(v + " ");
    }

    private double[] makeVector(int idx_fromPoint, Integer[] idx_toPoints) {
        double[] vectors = new double[idx_toPoints.length * 2];
        for (int i = 0; i < idx_toPoints.length; i++) {
            vectors[i * 2] = coordinates[idx_toPoints[i] * 2] - coordinates[idx_fromPoint * 2];
            vectors[i * 2 + 1] = coordinates[idx_toPoints[i] * 2 + 1] - coordinates[idx_fromPoint * 2 + 1];
        }
        return vectors;
    }

    public double[] makeVector(int idx_fromX, int idx_formY, int idx_toX, int idx_toY) {
        return new double[]{coordinates[idx_toX] - coordinates[idx_fromX],
                coordinates[idx_toY] - coordinates[idx_formY]};
    }

    private int[] cross_mul_sign(double[] vectors) {
        int[] res = new int[vectors.length / 2];
        for (int i = 0; i < res.length - 1; i++) {
            int idx = i * 2;
            res[i] = cross_mul_sign(vectors[idx], vectors[idx + 1], vectors[idx + 2], vectors[idx + 3]);
        }
        res[res.length - 1] = cross_mul_sign(vectors[vectors.length - 2], vectors[vectors.length - 1], vectors[0], vectors[1]);
        return res;
    }

    private int cross_mul_sign(double x0, double y0, double x1, double y1) {
        double res = x0 * y1 - y0 * x1;
        return res < 0 ? -1 : res > EPS ? 1 : 0;
    }
}

 


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值