单元格法求解多边形最大内接矩形问题【思路讲解+java实现】

news2024/11/26 4:50:54

问题描述

给定一个多边形的点集,希望找出多边形内部面积最大的矩形。该问题可能出现在,从一个多边形废料上面切割出一个最大的矩形,该矩形可以重复利用,解决该问题可以节约原材料,降低企业运作成本

问题解决方案

本文主要使用的方法是:将多边形离散化为多个单元格,然后判断哪些单元格处于多边形内部,最后通过遍历内部的单元格来找到面积最大的矩形

多边形网格化

已知每个多边形点集中的每个点的坐标为(y,z),通过遍历所有点,可以找出minY,maxY,minZ,maxZ,本文使用一个参数segmentNum来决定y轴方向上对多边形多离散的段数,dis=(maxY-minY)/segmentNum为步进长度,每间隔一个步进长度生成一条垂直于于y轴的扫描线,该扫描线会与多边形相交,本文将相交点的z坐标值生成为一条垂直于z轴的扫描线。最终待垂直于y轴或z轴的扫描线都生成完毕之后,会得到如下图所示的网格

在这里插入图片描述

segmentNum=5对应的网格

在这里插入图片描述

segmentNum=100对应的网格

需要注意的是,为了加快计算速度,可以使用一定的策略来筛选掉部分扫描线,如果两条扫描线之间间隔的距离非常相近,只需要使用其中一条即可,本文使用一个非常简单的策略,例如y=1.2、y=1.3,使用一个map来记录(int)(1.2)=1,因为int(1.3)也等于1,该扫描线会被舍弃

【java代码实现】

/**
 * 构建网络单元
 *
 * @param pointList
 * @return
 */
private Cell[][] constructCells(List<Point> pointList) {
    声明变量
    Cell[][] cells;

    开始构建
    List<Double> yList = new ArrayList<>();
    List<Double> zList = new ArrayList<>();
    Map<Integer, Boolean> yMap = new HashMap<>();
    Map<Integer, Boolean> zMap = new HashMap<>();
    for (Point point : pointList) {
        if (!yMap.containsKey((int) point.getY())) {
            yList.add(point.getY());
            yMap.put((int) point.getY(), true);
        }
        if (!zMap.containsKey((int) point.getZ())) {
            zList.add(point.getZ());
            zMap.put((int) point.getZ(), true);
        }
    }
    根据分段继续添加yList和zList
    double minY = Double.MAX_VALUE;
    double maxY = -Double.MAX_VALUE;
    double minZ = Double.MAX_VALUE;
    double maxZ = -Double.MAX_VALUE;
    for (Point point : pointList) {
        minY = Math.min(minY, point.getY());
        maxY = Math.max(maxY, point.getY());
        minZ = Math.min(minZ, point.getZ());
        maxZ = Math.max(maxZ, point.getZ());
    }
    ///根据分段数来确定y的增量
    double deltaOfY = (maxY - minY) * 1.0 / this.segmentNum;
    double curY = minY + deltaOfY;
    while (curY < maxY) {
        if (yMap.containsKey(curY)) {
            curY += deltaOfY;
            continue;
        }
        boolean isAdd = false;
        // 对于每一根y扫描线,都拿来和多边形的每条边相交一下,来找到z扫描线
        for (int i = 0; i < pointList.size(); i++) {
            int j = (i + 1) % pointList.size();
            //获取边的两个点
            Point point1 = pointList.get(i);
            Point point2 = pointList.get(j);
            double minYOfSide = Math.min(point1.getY(), point2.getY());
            double maxYOfSide = Math.max(point1.getY(), point2.getY());
            if (minYOfSide < curY && curY < maxYOfSide) {
                ///对边构建表达式
                double curZ = this.getZByYAndLineMessage(point1, point2, curY);
                if (curZ == Double.MAX_VALUE) {
                    continue;
                }
                if (!zMap.containsKey((int) curZ)) {
                    zList.add(curZ);
                    zMap.put((int) curZ, true);
                }
                isAdd = true;
            }
        }
        if (isAdd == true) {
            yList.add(curY);
            yMap.put((int) curY, true);
        }
        curY += deltaOfY;
    }
    //对yList、zList升序排序
    Collections.sort(yList);
    Collections.sort(zList);
    cells = new Cell[zList.size() - 1][yList.size() - 1];
    for (int j = 0; j < zList.size() - 1; j++) {
        for (int i = 0; i < yList.size() - 1; i++) {
            Point left_up_point = new Point(0, yList.get(i), zList.get(j));
            Point right_down_point = new Point(0, yList.get(i + 1), zList.get(j + 1));
            cells[j][i] = new Cell(left_up_point, right_down_point);
        }
    }
    return cells;
}

区分每个单元格是在多边形内部还是外部

本文使用的区分方式是,分别判断一个单元格的四个点是否都在多边形内部,判断一个点是否在多边形的内部可以参考文章射线法——判断一个点是否在多边形内部(适用于凸多边形和凹多边形)【关键原理解释+文字伪代码+java代码实现】,只要有一个点不在多边形内部,则该单元格被标记为0,代表其不在多边形内部。如果单元格的四个点都在多边形内部,也不足以说明单元格就真的在多边形内部,下图就是一个例子
在这里插入图片描述
因此还需要判断单元格的边和多边形的边是否有相交,如果有相交,则单元格不完全在多边形内部。等所有的单元格都被判断是否完整地处于多边形内部并被标记为1之后,得到下图
在这里插入图片描述
【java实现】

/**
 * 标记网络单元,当一个单元处于多边形内部时,将单元标记为 1,否则标记为 0
 *
 * @param pointList
 * @param cells
 */
private void markCellsLabel(List<Point> pointList, Cell[][] cells) throws Exception {
    long start = System.currentTimeMillis();
    for (int i = 0; i < cells.length; i++) {
        for (int j = 0; j < cells[i].length; j++) {
            cells[i][j].setLabel(this.getCellLabel(pointList, cells[i][j]));
        }
    }
    long end = System.currentTimeMillis();
    this.setCellsLabelTime += end - start;
}

/**
 * 判断单元格是否被纯包含于多边形内,是:返回1;否:返回0
 * 单元格中有一个点不在多边形内,单元格便是不在单元格内
 * type=0:第一次设置label时使用; type=1:reset label时使用;
 *
 * @param pointList
 * @param cell
 */
private int getCellLabel(List<Point> pointList, Cell cell) throws Exception {
    // 存储单元格的四个点
    Point[] pointArr = new Point[4];
    //左上角
    pointArr[0] = new Point(0, cell.getLeft_up_point().getY(), cell.getLeft_up_point().getZ());
    //右上角
    pointArr[1] = new Point(0, cell.getRight_down_point().getY(), cell.getLeft_up_point().getZ());
    //右下角
    pointArr[2] = new Point(0, cell.getRight_down_point().getY(), cell.getRight_down_point().getZ());
    //左下角
    pointArr[3] = new Point(0, cell.getLeft_up_point().getY(), cell.getRight_down_point().getZ());
    
    for (Point point : pointArr) {
        //只要有一个点不在多边形内,单元格被标记为不在多边形内
        if (PolygonUtil.judgeWhetherThePointInPolygon(pointList, point) == false) {
            return 0;
        }
    }

    //就算单元格的所有点都在多边形内部,不代表单元格就在多边形内了
    //再判断一下网络单元的四条边是否和线段有相交,且斜率不能相同,为了避免出现较特殊矩形时,单元的四个点都在多边形内部,但是实际上不在内部的情况出现
    for (int i = 0; i < pointArr.length; i++) {
        int j = (i + 1) % pointArr.length;
        for (int m = 0; m < pointList.size(); m++) {
            int n = (m + 1) % pointList.size();
            if (PolygonUtil.judgeWhetherTwoLineSegmentIsIntersect(pointArr[i], pointArr[j], pointList.get(m), pointList.get(n)) == true &&
                    (this.getKOfLine(pointArr[i], pointArr[j]) != this.getKOfLine(pointList.get(m), pointList.get(n)))) {
                //--if--当单元格的边和多边形有交叉,且不是平行的那种时,单元不在多边形内部
                return 0;
            }
        }
    }
//            System.out.println("单元格在多边形内部");
    return 1;
}

根据已标记单元格寻找最大内接矩形

本文寻找最大内接矩形的方式如下:遍历每个单元格,尝试将该单元格开始,然后向右、向下寻找单元格进行组合来找到该单元格最大的矩形。在遍历的途中,不断更新面积最大的矩形,遍历结束之后即可求得结果

当遍历到一个单元格时,从单元格开始,向右遍历每一列,找出每一列的对应的连续高度填充到heightArr中,如下图所示。同理,遍历每一行,找出每一行的连续宽度,填充到widthArr中,同时将每一行最大连续宽度的单元格所在的列索引填充到maxColumnNumArr
在这里插入图片描述
具体操作如下面的代码所示

/*
* 只需要设置一次heightArr,后面需要多少列直接拿就行
* 只需要设置一次widthArr,下一列的格子的所能找到的最大宽度直接为当前格子所能找到的最大宽度-当前格子宽度
*/
double[] widthArr = new double[cells.length - i];
// 存储每一行到达最大宽度时的列数
int[] maxColumnNumArr = new int[cells.length - i];
double[] heightArr = new double[cells[0].length - j];

/// 初始化heightArr
for (int x = j; x < heightArr.length + j; x++) {
   // --for--循环每一列
   double heightOfCurColumn = 0;
   for (int y = i; y < cells.length; y++) {
       // --for--循环每一行
       if (cells[y][x].getLabel() == 1) {
           heightOfCurColumn += cells[y][x].getHeight();
       } else {
           break;
       }
   }
   heightArr[x - j] = heightOfCurColumn;
   if (Math.abs(heightOfCurColumn - 0) < this.precisionError) {
       // 相当于这一列开始,后面的列全是默认为0,因为所形成的矩形需要被最矮的一列限制
       break;
   }
}

/// 初始化 widthArr 和 maxColumnNumArr
for (int m = i; m < widthArr.length + i; m++) {
   // --for--循环每一行
   double widthOfCurRow = 0;
   // 找最大宽度所到达的列数
   int maxColumnNum = j;
   int num = 0;
   for (int k = j; k < cells[0].length; k++) {
       // --for--循环每一列
       if (cells[m][k].getLabel() == 1) {
           widthOfCurRow += cells[m][k].getWidth();
           num++;
       } else {
           break;
       }
   }
   maxColumnNum += num > 0 ? num - 1 : 0;
   // 存储当前行宽度
   widthArr[m - i] = widthOfCurRow;
   maxColumnNumArr[m - i] = maxColumnNum;
}

当遍历到一个单元格时,还需要做如下事情来寻找当前单元格对应的最大矩形,因为文字实在太难描述,我直接贴代码了o(╥﹏╥)o

/// 找到最小的n
// 从widthArr和nArr来看
int minN = Integer.MAX_VALUE;
for (int index = 0; index < maxColumnNumArr.length; index++) {
   if (maxColumnNumArr[index] > 0) {
       minN = Math.min(minN, maxColumnNumArr[index]);
   } else {
       break;
   }
}
if (minN == Integer.MAX_VALUE) {
   // 将minN赋值为j-1,这样下面就无需循环寻找结果了
   minN = j - 1;
}
// 从heightArr来看
int minNOfHeightArr = j;
for (int k = 1; k < heightArr.length; k++) {
   if (heightArr[k] <= heightArr[k - 1]) {
       minNOfHeightArr++;
   } else {
       break;
   }
}
minN = Math.min(minN, minNOfHeightArr);

/// 【寻找最大面积的矩形】
int columnNum = 0;
int recordJ = j;
if (j <= minN) {
   while (j <= minN) {
       if (cells[i][j].getLabel() == 1 && cells[i][j].getMaxArea() > bestArea) {
           for (int m = i; m < widthArr.length + i; m++) {
               // --for--循环每一行
               double widthOfCurRow = widthArr[m - i];
               // 找最大宽度所到达的列数
               int maxColumnNum = maxColumnNumArr[m - i];
               if ((m - i - 1) >= 0 && maxColumnNum == maxColumnNumArr[m - i - 1]) {
                   // --if-- 最大列数和上一个的最大列数一样,可以直接跳过此轮循环,直接进入下一轮
                   continue;
               }
               if (Math.abs(widthOfCurRow - 0) < this.precisionError) {
                   break;
               } else {
                   // 当前行对应的最大宽度
                   double maxWid = widthOfCurRow;
                   // 寻找当前宽度对应的最小高度
                   double maxHei = Integer.MAX_VALUE;
                   boolean isAssign = false;
                   for (int k = columnNum; k < maxColumnNum - recordJ + 1; k++) {
                       maxHei = Math.min(maxHei, heightArr[k]);
                       isAssign = true;
                   }
                   // 更新最优解
                   if (maxWid * maxHei > bestArea && isAssign == true) {
                       bestArea = maxWid * maxHei;
                       bestRectangle = new LoadProductDto(maxWid, maxHei, cells[i][j].getLeft_up_point().getY(), cells[i][j].getLeft_up_point().getZ());
                   }
               }
           }
       }
       // 更新widthArr
       for (int m = i; m < widthArr.length + i; m++) {
           if (widthArr[m - i] > 0) {
               // 每一行的width减去当前列的宽度
               widthArr[m - i] -= cells[m][j].getWidth();
           } else {
               break;
           }
       }
       j++;
       columnNum++;
       break;
   }
   // 上面的j++已经超出minN了,重新回到minN,因为循环的时候,j会++,如果不回到minN,会漏掉一列
   j = minN;
}

代码中注释为【寻找最大面积的矩形】的代码片段主要在做如下的事情,可以结合下面的一系列图片来理解

在这里插入图片描述
【从第一列开始寻找矩形】
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
【从第二列开始寻找矩形】
在这里插入图片描述
---- 依次类推 ----
在这里插入图片描述
【上面的过程结束之后,下一个遍历的单元格如下图,原因是y已经变成了minN+1
在这里插入图片描述

你们可能会困惑,为啥需要这段代码来限制minN

// 从heightArr来看
int minNOfHeightArr = j;
for (int k = 1; k < heightArr.length; k++) {
   if (heightArr[k] <= heightArr[k - 1]) {
       minNOfHeightArr++;
   } else {
       break;
   }
}
minN = Math.min(minN, minNOfHeightArr);

假如不限制的话,遍历情况如图所示
在这里插入图片描述
再直接点说,会找不到如下面类型的结果
在这里插入图片描述

剪枝优化

当从网格的左上角遍历到网格的右下角时,能找到的矩形实际上已经非常小了,如果能提前预知一个单元格所能找到的最大矩形面积已经小于当前所找到的最大矩形面积的话,则该单元格无需进行遍历,直接跳过即可

本文使用递推的方法来设置一个单元格所能找到的理想最优矩形面积,代码如下:

/**
* 填充单元格的理想最大面积
*
* @param cells
*/
private void fillCellsMaxArea(Cell[][] cells) {
    填补每个单元格所能找到的理想最大面积(递推)
   /// 初始化网络单元的最后一行和最后一列
   // 初始化最后一行
   for (int i = 0; i < cells[cells.length - 1].length; i++) {
       //对每一列的最后一个单元进行处理
       Cell cell = cells[cells.length - 1][i];
       cell.setMaxArea(cell.getWidth() * cell.getHeight() * cell.getLabel());
   }
   // 初始化最后一列
   for (int i = 0; i < cells.length; i++) {
       //对每一行的最后一个单元进行处理
       Cell cell = cells[i][cells[0].length - 1];
       cell.setMaxArea(cell.getWidth() * cell.getHeight() * cell.getLabel());
   }
   /// 填充其他单元格的理想最大面积
   for (int i = cells.length - 2; i >= 0; i--) {
       for (int j = cells[0].length - 2; j >= 0; j--) {
           Cell cell = cells[i][j];
           //先填充自己的面积
           cell.setMaxArea(cell.getWidth() * cell.getHeight() * cell.getLabel());
           //再添加自己下面的单元格maxArea和右侧的单元格的maxArea
           if (i + 1 < cells.length) {
               cell.setMaxArea(cell.getMaxArea() + cells[i + 1][j].getMaxArea());
           }
           if (j + 1 < cells[0].length) {
               cell.setMaxArea(cell.getMaxArea() + cells[i][j + 1].getMaxArea());
           }
       }
   }
}

当满足单元格的标记为1,且理想最大矩形面积超过当前已经找到的最大矩形面积,才会去根据该单元格去执行上面的【根据已标记单元格寻找最大内接矩形】步骤

// 对每个被标记为1的单元格,将其向右、向下遍历,计算形成的最大矩形
for (int i = 0; i < cells.length; i++) {
    for (int j = 0; j < cells[0].length; j++) {
        if (cells[i][j].getLabel() == 1 && cells[i][j].getMaxArea() > bestArea) {
            // 根据已标记单元格寻找最大内接矩形
        }
    }
}

多角度旋转

上面的方法结束之后,只能求得一个角度下的最大内接矩形,会导致下图的情况发生

在这里插入图片描述
本文通过每旋转5°,求解一个最大内接矩形,最终选择最大的那个矩形作为最终结果,当然,如果想要找到更好的结果,可以不断缩小旋转角度,当然,计算时间也会上涨很多
在这里插入图片描述

案例测试

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码实现

package com.ruoyi.algorithm.get_maximumArea_rectangles_in_a_simple_polygon.version.v1;

import com.ruoyi.algorithm.common.entity.Cell;
import com.ruoyi.algorithm.common.entity.GetMaxRectangleInSimplePolygonResult;
import com.ruoyi.algorithm.common.entity.LoadProductDto;
import com.ruoyi.algorithm.common.entity.Point;

import java.util.*;

/**
 * 获取多边形内的最大内接矩形
 */
public class GetMaximumAreaRectangleInASimplePolygonApi {

    /**
     * 对边进行分段处理,增加更多的点
     */
    private int segmentNum = 100;
    /**
     * 是否打印标记矩阵
     */
    private boolean isPrintLabelMatrix = false;
    /**
     * paintType=0:画未补充点的线;paintType=1:画补充点后的线
     */
    private int paintType = 0;
    /**
     * 旋转度数
     */
    private int rotateDegreeDelta = 5;
    /**
     * 精度误差
     */
    private double precisionError = 0.0001;
    /**
     * 标记单元格时间
     */
    private long setCellsLabelTime = 0;
    /**
     * 根据已有解重置单元格标记的时间
     */
    private long resetCellsLabelTime = 0;
    /**
     * 给定单元格,寻找解的时间
     */
    private long searchSolutionTime = 0;

    /**
     * 且多边形的最大内接矩形
     *
     * @param pointList         顺时针或逆时针将点集传进来
     * @param bestRectangleList 可能有多个相等的内接矩形,都存储下来
     */
    public GetMaxRectangleInSimplePolygonResult solve(List<Point> pointList, List<LoadProductDto> bestRectangleList) throws Exception {
        System.out.println();

        long start = System.currentTimeMillis();

        声明变量
        List<Point> solution = new ArrayList<>();
        GetMaxRectangleInSimplePolygonResult getMaxRectangleInSimplePolygonResult = new GetMaxRectangleInSimplePolygonResult();

        存储到结果的数据
        List<Double> yList = new ArrayList<>();
        List<Double> zList = new ArrayList<>();
        double minY = Double.MAX_VALUE;
        double maxY = -Double.MAX_VALUE;
        double minZ = Double.MAX_VALUE;
        double maxZ = -Double.MAX_VALUE;
        Cell[][] cells = null;
        for (Point point : pointList) {
            minY = Math.min(minY, point.getY());
            maxY = Math.max(maxY, point.getY());
            minZ = Math.min(minZ, point.getZ());
            maxZ = Math.max(maxZ, point.getZ());
        }

        找最大矩形
        ///声明变量
        //记录最大矩形的面积
        double bestArea = 0;
        //记录最佳旋转角度
        int bestRotateDegree = 0;
        LoadProductDto bestRectangle = null;
        List<Point> bestSolution = new ArrayList<>();
        //记录当前所遍历到的角度
        int rotateDegree = 0;
        int endRotateDegree = 360;
        while (rotateDegree <= endRotateDegree) {
//            System.out.println("当前角度:"+rotateDegree);
            //每旋转rotateDegreeDelta,求一次结果,取中间的最优解
            LoadProductDto curRectangle = this.solveWithAppointRotateDegree(Point.cloneList(pointList), rotateDegree, solution);
            if (curRectangle == null) {
                rotateDegree += this.rotateDegreeDelta;
                continue;
            }
            //更新结果
            double curArea = curRectangle.getWidth() * curRectangle.getHeight();
            if ((bestRectangle == null) || (curArea > bestArea)) {
                System.out.println("找到矩形1-------------------------");
                //找curRectangle的4个角点
                List<Point> curSolution = new ArrayList<>();
                Point leftUpPoint = new Point(0, curRectangle.getY(), curRectangle.getZ());
                Point rightUpPoint = new Point(0, curRectangle.getY() + curRectangle.getWidth(), curRectangle.getZ());
                Point rightDownPoint = new Point(0, curRectangle.getY() + curRectangle.getWidth(), curRectangle.getZ() + curRectangle.getHeight());
                Point leftDownPoint = new Point(0, curRectangle.getY(), curRectangle.getZ() + curRectangle.getHeight());
                Collections.addAll(curSolution, leftDownPoint, rightDownPoint, rightUpPoint, leftUpPoint);
                //对结果进行反旋转
                for (Point point : curSolution) {
                    double[] arr = this.rotate(point.getY(), point.getZ(), 0, 0, -rotateDegree);
                    point.setY(arr[0]);
                    point.setZ(arr[1]);
                }
                //替换结果
                bestArea = curArea;
                bestRectangle = curRectangle;
                bestSolution.clear();
                bestSolution.addAll(curSolution);
                bestRotateDegree = rotateDegree;
                bestRectangle.setBestRotateDegree(bestRotateDegree);
            }
            rotateDegree += this.rotateDegreeDelta;
        }
        if (bestRectangle != null) {
            //找到解,对解进行存储
            solution = Point.cloneList(bestSolution);
            System.out.println("找到矩形2-------------------------");
            System.out.println("最优旋转角度:" + bestRotateDegree);
            System.out.println("bestSolution:" + bestSolution);
            bestRectangleList.add(bestRectangle);
            ///存储最优解,需要将网格那些也保存下来
            if (this.paintType == 1) {
                minY = Double.MAX_VALUE;
                maxY = -Double.MAX_VALUE;
                minZ = Double.MAX_VALUE;
                maxZ = -Double.MAX_VALUE;
                // 将零件点旋转一下
                for (Point point : pointList) {
                    double[] arr = this.rotate(point.getY(), point.getZ(), 0, 0, bestRotateDegree);
                    point.setY(arr[0]);
                    point.setZ(arr[1]);
                    minY = Math.min(minY, point.getY());
                    maxY = Math.max(maxY, point.getY());
                    minZ = Math.min(minZ, point.getZ());
                    maxZ = Math.max(maxZ, point.getZ());
                }
                // 将结旋转一下
                for (Point point : solution) {
                    double[] arr = this.rotate(point.getY(), point.getZ(), 0, 0, bestRotateDegree);
                    point.setY(arr[0]);
                    point.setZ(arr[1]);
                }
                //构建网络单元
                cells = this.constructCells(pointList);
                //标记单元格,所有被全包含于多边形的单元格被标记为1
                this.markCellsLabel(pointList, cells);
            }
        }
        getMaxRectangleInSimplePolygonResult.setSolution(solution);
        getMaxRectangleInSimplePolygonResult.setPointList(pointList);
        getMaxRectangleInSimplePolygonResult.setBestRectangleList(bestRectangleList);
        getMaxRectangleInSimplePolygonResult.setYList(yList);
        getMaxRectangleInSimplePolygonResult.setZList(zList);
        getMaxRectangleInSimplePolygonResult.setWidth(maxY - minY);
        getMaxRectangleInSimplePolygonResult.setHeight(maxZ - minZ);
        getMaxRectangleInSimplePolygonResult.setMinY(minY);
        getMaxRectangleInSimplePolygonResult.setMinZ(minZ);
        getMaxRectangleInSimplePolygonResult.setCells(cells);
        long end = System.currentTimeMillis();
        double calculateTime = (end - start) * 1.0 / 1000;
        getMaxRectangleInSimplePolygonResult.setCalculateTime(calculateTime);

        System.out.println("结果输出--------------------------------------------------");
        System.out.println("计算时间:" + calculateTime + "s");
        System.out.println("标记网络单元总标记时间:" + this.setCellsLabelTime * 1.0 / 1000 + "s");
        System.out.println("重置网络单元总标记时间:" + this.resetCellsLabelTime * 1.0 / 1000 + "s");
        System.out.println("给定网络搜索解时间:" + this.searchSolutionTime * 1.0 / 1000 + "s");

        return getMaxRectangleInSimplePolygonResult;
    }

    /**
     * 指定旋转角度,求当前旋转角度的最大矩形
     *
     * @param pointList
     * @param rotateDegree
     * @return
     */
    private LoadProductDto solveWithAppointRotateDegree(List<Point> pointList, int rotateDegree, List<Point> solution) throws Exception {

         数据处理
        // 将点旋转一下
        for (Point point : pointList) {
            double[] arr = this.rotate(point.getY(), point.getZ(), 0, 0, rotateDegree);
            point.setY(arr[0]);
            point.setZ(arr[1]);
        }

         声明变量
        double bestArea = 0;
        LoadProductDto bestRectangle = null;

        // 构建单元格
        Cell[][] cells = this.constructCells(pointList);
        // 标记单元格,所有被全包含于多边形的单元格被标记为1
        this.markCellsLabel(pointList, cells);
        if (this.isPrintLabelMatrix == true) {
            System.out.println("输出真实标记矩阵,在多边形内的单元被标记为1");
            for (int i = 0; i < cells.length; i++) {
                for (int j = 0; j < cells[i].length; j++) {
                    System.out.printf(String.valueOf(cells[i][j].getLabel()) + "\t");
                }
                System.out.println();
            }
        }
        // 填充单元格的最大面积(搜索到右下角的单元格时,如果知道所能找到的矩形一定不可能大于已经搜索到的矩形时,直接停止该单元格的搜索)
        this.fillCellsMaxArea(cells);
        // 对每个被标记为1的单元格,将其向右、向下遍历,计算形成的最大矩形
        for (int i = 0; i < cells.length; i++) {
            for (int j = 0; j < cells[0].length; j++) {
                if (cells[i][j].getLabel() == 1 && cells[i][j].getMaxArea() > bestArea) {
                    /*
                     * 只需要设置一次heightArr,后面需要多少列直接拿就行
                     * 只需要设置一次widthArr,下一列的格子的所能找到的最大宽度直接为当前格子所能找到的最大宽度-当前格子宽度
                     */
                    long searchSolutionTimeStart = System.currentTimeMillis();
                    double[] widthArr = new double[cells.length - i];
                    // 存储每一行到达最大宽度时的列数
                    int[] maxColumnNumArr = new int[cells.length - i];
                    double[] heightArr = new double[cells[0].length - j];

                    /// 初始化heightArr
                    for (int x = j; x < heightArr.length + j; x++) {
                        // --for--循环每一列
                        double heightOfCurColumn = 0;
                        for (int y = i; y < cells.length; y++) {
                            // --for--循环每一行
                            if (cells[y][x].getLabel() == 1) {
                                heightOfCurColumn += cells[y][x].getHeight();
                            } else {
                                break;
                            }
                        }
                        heightArr[x - j] = heightOfCurColumn;
                        if (Math.abs(heightOfCurColumn - 0) < this.precisionError) {
                            // 相当于这一列开始,后面的列全是默认为0,因为所形成的矩形需要被最矮的一列限制
                            break;
                        }
                    }

                    /// 初始化 widthArr 和 maxColumnNumArr
                    for (int m = i; m < widthArr.length + i; m++) {
                        // --for--循环每一行
                        double widthOfCurRow = 0;
                        // 找最大宽度所到达的列数
                        int maxColumnNum = j;
                        int num = 0;
                        for (int k = j; k < cells[0].length; k++) {
                            // --for--循环每一列
                            if (cells[m][k].getLabel() == 1) {
                                widthOfCurRow += cells[m][k].getWidth();
                                num++;
                            } else {
                                break;
                            }
                        }
                        maxColumnNum += num > 0 ? num - 1 : 0;
                        // 存储当前行宽度
                        widthArr[m - i] = widthOfCurRow;
                        maxColumnNumArr[m - i] = maxColumnNum;
                    }

                    /// 找到最小的n
                    // 从widthArr和nArr来看
                    int minN = Integer.MAX_VALUE;
                    for (int index = 0; index < maxColumnNumArr.length; index++) {
                        if (maxColumnNumArr[index] > 0) {
                            minN = Math.min(minN, maxColumnNumArr[index]);
                        } else {
                            break;
                        }
                    }
                    if (minN == Integer.MAX_VALUE) {
                        // 将minN赋值为j-1,这样下面就无需循环寻找结果了
                        minN = j - 1;
                    }
                    // 从heightArr来看
                    int minNOfHeightArr = j;
                    for (int k = 1; k < heightArr.length; k++) {
                        if (heightArr[k] <= heightArr[k - 1]) {
                            minNOfHeightArr++;
                        } else {
                            break;
                        }
                    }
                    minN = Math.min(minN, minNOfHeightArr);

                    /// 寻找最大面积的矩形
                    int columnNum = 0;
                    int recordJ = j;
                    if (j <= minN) {
                        while (j <= minN) {
                            if (cells[i][j].getLabel() == 1 && cells[i][j].getMaxArea() > bestArea) {
                                for (int m = i; m < widthArr.length + i; m++) {
                                    // --for--循环每一行
                                    double widthOfCurRow = widthArr[m - i];
                                    // 找最大宽度所到达的列数
                                    int maxColumnNum = maxColumnNumArr[m - i];
                                    if ((m - i - 1) >= 0 && maxColumnNum == maxColumnNumArr[m - i - 1]) {
                                        // --if-- 最大列数和上一个的最大列数一样,可以直接跳过此轮循环,直接进入下一轮
                                        continue;
                                    }
                                    if (Math.abs(widthOfCurRow - 0) < this.precisionError) {
                                        break;
                                    } else {
                                        // 当前行对应的最大宽度
                                        double maxWid = widthOfCurRow;
                                        // 寻找当前宽度对应的最小高度
                                        double maxHei = Integer.MAX_VALUE;
                                        boolean isAssign = false;
                                        for (int k = columnNum; k < maxColumnNum - recordJ + 1; k++) {
                                            maxHei = Math.min(maxHei, heightArr[k]);
                                            isAssign = true;
                                        }
                                        // 更新最优解
                                        if (maxWid * maxHei > bestArea && isAssign == true) {
                                            bestArea = maxWid * maxHei;
                                            bestRectangle = new LoadProductDto(maxWid, maxHei, cells[i][j].getLeft_up_point().getY(), cells[i][j].getLeft_up_point().getZ());
                                        }
                                    }
                                }
                            }
                            // 更新widthArr
                            for (int m = i; m < widthArr.length + i; m++) {
                                if (widthArr[m - i] > 0) {
                                    // 每一行的width减去当前列的宽度
                                    widthArr[m - i] -= cells[m][j].getWidth();
                                } else {
                                    break;
                                }
                            }
                            j++;
                            columnNum++;
                            break;
                        }
                        // 上面的j++已经超出minN了,重新回到minN,因为循环的时候,j会++,如果不回到minN,会漏掉一列
                        j = minN;
                    }
                    long searchSolutionTimeEnd = System.currentTimeMillis();
                    this.searchSolutionTime += searchSolutionTimeEnd - searchSolutionTimeStart;
                }
            }
        }

        return bestRectangle;
    }

    /**
     * 构建网络单元
     *
     * @param pointList
     * @return
     */
    private Cell[][] constructCells(List<Point> pointList) {
        声明变量
        Cell[][] cells;

        开始构建
        List<Double> yList = new ArrayList<>();
        List<Double> zList = new ArrayList<>();
        Map<Integer, Boolean> yMap = new HashMap<>();
        Map<Integer, Boolean> zMap = new HashMap<>();
        for (Point point : pointList) {
            if (!yMap.containsKey((int) point.getY())) {
                yList.add(point.getY());
                yMap.put((int) point.getY(), true);
            }
            if (!zMap.containsKey((int) point.getZ())) {
                zList.add(point.getZ());
                zMap.put((int) point.getZ(), true);
            }
        }
        根据分段继续添加yList和zList
        double minY = Double.MAX_VALUE;
        double maxY = -Double.MAX_VALUE;
        double minZ = Double.MAX_VALUE;
        double maxZ = -Double.MAX_VALUE;
        for (Point point : pointList) {
            minY = Math.min(minY, point.getY());
            maxY = Math.max(maxY, point.getY());
            minZ = Math.min(minZ, point.getZ());
            maxZ = Math.max(maxZ, point.getZ());
        }
        ///根据分段数来确定y的增量
        double deltaOfY = (maxY - minY) * 1.0 / this.segmentNum;
        double curY = minY + deltaOfY;
        while (curY < maxY) {
            if (yMap.containsKey(curY)) {
                curY += deltaOfY;
                continue;
            }
            boolean isAdd = false;
            // 对于每一根y扫描线,都拿来和多边形的每条边相交一下,来找到z扫描线
            for (int i = 0; i < pointList.size(); i++) {
                int j = (i + 1) % pointList.size();
                //获取边的两个点
                Point point1 = pointList.get(i);
                Point point2 = pointList.get(j);
                double minYOfSide = Math.min(point1.getY(), point2.getY());
                double maxYOfSide = Math.max(point1.getY(), point2.getY());
                if (minYOfSide < curY && curY < maxYOfSide) {
                    ///对边构建表达式
                    double curZ = this.getZByYAndLineMessage(point1, point2, curY);
                    if (curZ == Double.MAX_VALUE) {
                        continue;
                    }
                    if (!zMap.containsKey((int) curZ)) {
                        zList.add(curZ);
                        zMap.put((int) curZ, true);
                    }
                    isAdd = true;
                }
            }
            if (isAdd == true) {
                yList.add(curY);
                yMap.put((int) curY, true);
            }
            curY += deltaOfY;
        }
        //对yList、zList升序排序
        Collections.sort(yList);
        Collections.sort(zList);
        cells = new Cell[zList.size() - 1][yList.size() - 1];
        for (int j = 0; j < zList.size() - 1; j++) {
            for (int i = 0; i < yList.size() - 1; i++) {
                Point left_up_point = new Point(0, yList.get(i), zList.get(j));
                Point right_down_point = new Point(0, yList.get(i + 1), zList.get(j + 1));
                cells[j][i] = new Cell(left_up_point, right_down_point);
            }
        }
        return cells;
    }

    /**
     * 标记网络单元,当一个单元处于多边形内部时,将单元标记为1,否则标记为0
     *
     * @param pointList
     * @param cells
     */
    private void markCellsLabel(List<Point> pointList, Cell[][] cells) throws Exception {
        long start = System.currentTimeMillis();
        for (int i = 0; i < cells.length; i++) {
            for (int j = 0; j < cells[i].length; j++) {
                cells[i][j].setLabel(this.getCellLabel(pointList, cells[i][j]));
            }
        }
        long end = System.currentTimeMillis();
        this.setCellsLabelTime += end - start;
    }

    /**
     * 判断单元格是否被纯包含于多边形内,是:返回1;否:返回0
     * 单元格中有一个点不在多边形内,单元格便是不在单元格内
     * type=0:第一次设置label时使用; type=1:reset label时使用;
     *
     * @param pointList
     * @param cell
     */
    private int getCellLabel(List<Point> pointList, Cell cell) throws Exception {
        // 存储单元格的四个点
        Point[] pointArr = new Point[4];
        //左上角
        pointArr[0] = new Point(0, cell.getLeft_up_point().getY(), cell.getLeft_up_point().getZ());
        //右上角
        pointArr[1] = new Point(0, cell.getRight_down_point().getY(), cell.getLeft_up_point().getZ());
        //右下角
        pointArr[2] = new Point(0, cell.getRight_down_point().getY(), cell.getRight_down_point().getZ());
        //左下角
        pointArr[3] = new Point(0, cell.getLeft_up_point().getY(), cell.getRight_down_point().getZ());

        for (Point point : pointArr) {
            //只要有一个点不在多边形内,单元格被标记为不在多边形内
            if (PolygonUtil.judgeWhetherThePointInPolygon(pointList, point) == false) {
                return 0;
            }
        }

        //就算单元格的所有点都在多边形内部,不代表单元格就在多边形内了
        //再判断一下网络单元的四条边是否和线段有相交,且斜率不能相同,为了避免出现较特殊矩形时,单元的四个点都在多边形内部,但是实际上不在内部的情况出现
        for (int i = 0; i < pointArr.length; i++) {
            int j = (i + 1) % pointArr.length;
            for (int m = 0; m < pointList.size(); m++) {
                int n = (m + 1) % pointList.size();
                if (PolygonUtil.judgeWhetherTwoLineSegmentIsIntersect(pointArr[i], pointArr[j], pointList.get(m), pointList.get(n)) == true &&
                        (this.getKOfLine(pointArr[i], pointArr[j]) != this.getKOfLine(pointList.get(m), pointList.get(n)))) {
                    //--if--当单元格的边和多边形有交叉,且不是平行的那种时,单元不在多边形内部
                    return 0;
                }
            }
        }
//            System.out.println("单元格在多边形内部");
        return 1;
    }

    /**
     * 填充单元格的理想最大面积
     *
     * @param cells
     */
    private void fillCellsMaxArea(Cell[][] cells) {
         填补每个单元格所能找到的理想最大面积(递推)
        /// 初始化网络单元的最后一行和最后一列
        // 初始化最后一行
        for (int i = 0; i < cells[cells.length - 1].length; i++) {
            //对每一列的最后一个单元进行处理
            Cell cell = cells[cells.length - 1][i];
            cell.setMaxArea(cell.getWidth() * cell.getHeight() * cell.getLabel());
        }
        // 初始化最后一列
        for (int i = 0; i < cells.length; i++) {
            //对每一行的最后一个单元进行处理
            Cell cell = cells[i][cells[0].length - 1];
            cell.setMaxArea(cell.getWidth() * cell.getHeight() * cell.getLabel());
        }
        /// 填充其他单元格的理想最大面积
        for (int i = cells.length - 2; i >= 0; i--) {
            for (int j = cells[0].length - 2; j >= 0; j--) {
                Cell cell = cells[i][j];
                //先填充自己的面积
                cell.setMaxArea(cell.getWidth() * cell.getHeight() * cell.getLabel());
                //再添加自己下面的单元格maxArea和右侧的单元格的maxArea
                if (i + 1 < cells.length) {
                    cell.setMaxArea(cell.getMaxArea() + cells[i + 1][j].getMaxArea());
                }
                if (j + 1 < cells[0].length) {
                    cell.setMaxArea(cell.getMaxArea() + cells[i][j + 1].getMaxArea());
                }
            }
        }
    }


    /**
     * 求一条直线的斜率
     *
     * @param p1
     * @param p2
     * @return
     */
    private double getKOfLine(Point p1, Point p2) {
        return MathUtil.getKOfLine(p1.getY(), p1.getZ(), p2.getY(), p2.getZ());
    }

    /**
     * 平面上一点x1,y1,绕平面上另一点x2,y2顺时针旋转rotateDegree角度,求旋转后的x1,y1对应的坐标x,y
     *
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param rotateDegree
     * @return arr[0]:x;arr[1]:y
     */
    private double[] rotate(double x1, double y1, double x2, double y2, int rotateDegree) {
        double[] arr = new double[2];
        //根据角度求弧度
        double radian = (rotateDegree * 1.0 / 180) * Math.PI;
        //旋转
        arr[0] = (x1 - x2) * Math.cos(radian) - (y1 - y2) * Math.sin(radian) + x2;
        arr[1] = (y1 - y2) * Math.cos(radian) + (x1 - x2) * Math.sin(radian) + y2;
        return arr;
    }

    /**
     * 传入一条线段,然后根据已知的y来求线段上的z
     *
     * @param p1
     * @param p2
     * @param y
     * @return
     */
    private double getZByYAndLineMessage(Point p1, Point p2, double y) {
        double k = this.getKOfLine(p1, p2);
        if (Math.abs(k - Double.MAX_VALUE) < this.precisionError) {
            return Double.MAX_VALUE;
        } else if (Math.abs(k - 0) < this.precisionError) {
            return p1.getZ();
        } else {
            //截距
            double b = p2.getZ() - k * p2.getY();
            return k * y + b;
        }
    }

    public GetMaximumAreaRectangleInASimplePolygonApi() {
    }
}

代码中使用的一些工具类麻烦查看文章射线法——判断一个点是否在多边形内部(适用于凸多边形和凹多边形)【关键原理解释+文字伪代码+java代码实现】

说明

本文的算法算是比较粗暴的求解算法,加上本人代码能力不够强,编码时间短,部分地方的处理还有待提高,可能导致部分数据计算时间较长,假如读者们有什么更好的建议也希望可以不吝赐教。如果你们觉得有帮助,也希望可以点个赞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1005290.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决Selenium元素拖拽不生效Bug

前几天在使用Selenium进行元素拖拽操作时&#xff0c;发现Selenium自带的元素拖拽方法&#xff08;dragAndDrop()&#xff09;不生效&#xff0c;网上的回答也是五花八门&#xff0c;比较混乱&#xff0c;尝试了以下几种方法均无法解决。 方案1&#xff1a;通过dragAndDrop()方…

群晖NAS如何在内网部署HTTPS服务让浏览器信任证书

前言 最近在折腾内部部署Web服务。通过Vue实现一个H5的内部的管理服务。但在实际部署过程中由于种种原因&#xff0c;必须部署成Https服务。但在部署成Https服务后&#xff0c;由于没有HTTPS证书&#xff0c;每次进入页面都会被浏览器拦截。使用起来非常不便。于是开始各种Goo…

显示器显示的画面突然偏红色如何解决

显示器显示的画面突然偏红色如何解决 1. 概述2. 解决方法结束语 1. 概述 显示器显示的画面突然偏红色 &#xff0c;使用向日葵远程电脑&#xff0c;看到的画面是正常的&#xff0c;但是显示器上的画面确还是骗红的&#xff0c;这时候就需要看一下是不是开启了系统也夜间模式&a…

Linux之使用LAMP搭建私有云存储

目录 Linux之使用LAMP搭建私有云存储 恢复快照&#xff0c;关闭安全软件 搭建LAMP环境 下载安装依赖包 下载nextcloud软件 解压nextcloud 设置nextcloud安装命令权限 数据库配置 设置数据库 重启数据库 配置httpd 重启httpd服务 安装 打开浏览器后输入服务器IP地址…

LeetCode(力扣)1005. K 次取反后最大化的数组和Python

LeetCode1005. K 次取反后最大化的数组和 题目链接代码 题目链接 https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/ 代码 class Solution:def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:nums.sort(keylambda x: abs(x),…

一款攻击面管理必备工具-Goby

引言 在正式介绍Goby之前,我们先简单介绍下网络功防的基本常识: 首先,对于攻击方,我们需要了解一个常见的攻击过程: 攻击者需要明确探测目标,锁定目标的地理位置、IP、域名等基本信息;对目标ip、域名进行存活性判定,对其操作系统、开放端口、服务等信息进行识别;针对…

【1462. 课程表 IV】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 你总共需要上 numCourses 门课&#xff0c;课程编号依次为 0 到 numCourses-1 。你会得到一个数组 prerequisite &#xff0c;其中 prerequisites[i] [ai, bi] 表示如果你想选 bi 课程&#xff0c;你…

你真的会报bug吗?常见10条错误

几乎每一位测试员&#xff0c;都会因为自己发现了一个bug而沾沾自喜&#xff0c;然后迫不及待地报bug。此时&#xff0c;恨不得有个喇叭&#xff0c;在整个办公室广而告之一下&#xff0c;实际上这样的行为并不可取&#xff0c;因为软件开发人员最怕的就是思路被打断&#xff0…

关于vue封装form表单单向流数据问题

vue在封装form表单业务组件问题时&#xff0c;传参的方式可能是 组件接收的形式这样很容易打破vue 的单向流数据规则&#xff0c;这样写肯定不会影响功能&#xff0c;只不过代码离屎山越来越近。 创建一个计算属性&#xff0c;get获取form 再利用 proxy 代理去代理这个对象&am…

DBC文件解析

一.candb 二.DBC文件解析 NS_ : NS_DESC_&#xff1a;用于描述网络信号的描述信息。 CM_&#xff1a;用于定义信号的描述信息。 BA_DEF_&#xff1a;定义信号的属性。 BA_&#xff1a;为信号属性定义值。 VAL_&#xff1a;为信号的枚举值定义标签。 CAT_DEF_&#xff1a;定义…

SoftwareTest2 - 软件测试相关概念

软件测试答疑篇 目标一 . 什么是需求二 . 测试用例三 . 什么是 BUG四 . 开发模型4.1 软件的生命周期需求分析计划设计编码测试运行维护 4.2 软件测试的生命周期需求分析测试计划测试设计与开发执行测试测试评估 4.3 常见模型瀑布模型螺旋模型增量模型、迭代模型敏捷模型scrum模…

GO语言篇之embed

GO语言篇之embed 文章目录 GO语言篇之embed前言目录结构文件转[]byte文件转string多文件转embed.FS目录转embed.FS文件和目录组合的方式转embed.FS 前言 embed是Go语言提供的一种机制&#xff0c;可使静态文件或文件夹嵌入Go语言程序中&#xff0c;使我们Go语言的可执行文件包…

kibana报错内存溢出问题解决

一、背景&#xff1a; kibana内存溢出&#xff0c;进程被kill掉&#xff0c;导致前端页面访问不到。 报错内容 二、报错原因&#xff1a; 发现是前端 js 报的内存 oom 异常&#xff0c;通过网上资料发现node.js 的默认内存大小为1.4G Node 中通过 JavaScript 使用内存时只能…

PCL入门(六):深度图提取边界

目录 1. 深度图介绍2. 深度图生成3. 边界提取 1. 深度图介绍 参考《02-深度图》 深度图像&#xff08;Depth Images&#xff09;也被称为距离影像&#xff08;Range Image&#xff09;&#xff0c;是指将从图像采集器到场景中各点的距离值作为像素值的图像&#xff0c;它直接…

CentOS7上从0开始搭建Zookeeper集群

CentOS7上搭建Zookeeper集群 环境准备安装jdk安装zookeeper下载zookeeper解压zookeeper修改zookeeper配置文件 搭建zookeeper集群修改zoo.cfg文件添加myid文件启动zookeeper集群 环境准备 首先你需要准备三台zookeeper&#xff08;待会会讲zookeeper的安装流程&#xff09;&am…

运算放大器典型应用(二)

文章目录 十、采样保持电路十一、有源滤波电路二阶有源低通滤波器问题二阶截至频率如何算 十、采样保持电路 十一、有源滤波电路 给单片机供电R一般大于4.1Ω小于10Ω&#xff0c;太大会产生功耗 二阶有源低通滤波器问题 二阶截至频率如何算 通频带比较窄可以用这种&#xff0…

IDEA启动时选择项目

IDEA默认情况下&#xff0c;启动时会选择上一次打开的项目继续。如果我们希望每次启动时都需要手动选择项目&#xff0c;可以按照下列顺序修改&#xff1a; 【File】-【Settings】-【Apperance&Behavior】-【System Settings】-【Startup/Shutdown】 取消选中Reopen last…

硬件总线基础07:PCIe总线基础-事务层(2)

说在开头&#xff1a;关于哲学 在《东邪西毒》电影里欧阳锋说&#xff1a;“看来你的年纪也有四十出头了&#xff0c;这四十多年来&#xff0c;总有些事你是不愿再提&#xff0c;或是有些人不想再见&#xff0c;有的人曾经对不起你&#xff0c;也许你想过杀了他们&#xff0c;…

PyCharm Clion IDEA专业版安装图文教程

1.下载专业版 PyCharm Download PyCharm: Python IDE for Professional Developers by JetBrains 2.以2023.1.4为例 3.next 4.next 5.next 6.Install 7.Finish 8.Activate 链接&#xff1a;https://pan.baidu.com/s/1N9n8wGgkvjfOX8oDrfX2Hw 提取码&#xff1a;yyds

知识图谱实战应用28-基于py2neo的ICD-11疾病分类的知识图谱的查询与问答实战应用

大家好,我是微学AI,今天给大家介绍一下知识图谱实战应用28-基于py2neo的ICD-11疾病分类的知识图谱的查询与问答实战应用。使用基于py2neo的ICD-11疾病分类知识图谱,我们能够像探索一座生物医学宇宙般,穿梭在各种疾病之间。这个神奇的图谱可以帮助我们揭示各种疾病之间复杂而…