文章目录

  • 1 前言
  • 2 代码迁移
  • 3 蚁群算法
  • 3.1 蚂蚁类 Ant
  • 3.2 蚁群算法类 ACO_Packing
  • 4 运行结果
  • 5 后话



【运筹优化】求解二维矩形装箱问题的算法合辑(Java代码实现)

1 前言

之前我已经写过一篇禁忌搜索算法求解二维矩形装箱问题(java代码实现),如果有对二维矩形装箱问题的背景不是很了解的朋友可以去看看


2 代码迁移

项目的大体框架(一些实体类,数据读取类等)和禁忌搜索算法求解二维矩形装箱问题(java代码实现)中的差不多,所以本文只提供蚁群算法的核心代码,大家用的时候只需要将两个禁忌搜索算法类替换为下文中的蚁群算法类即可。

二维装箱问题 java 二维装箱问题代码_二维矩形装箱

3 蚁群算法

3.1 蚂蚁类 Ant

/**
 * @Author:WSKH
 * @ClassName:Ant
 * @ClassType:蚂蚁类
 * @Description:
 * @Date:2022/5/29/15:04
 */
@Data
public class Ant implements Cloneable {

    private List<Square> squareList; // 矩形集合
    private List<PlaceSquare> placeSquareList; // 已经放置的矩形集合
    private List<Integer> tabu; // 已经放置的矩形的索引
    private List<Integer> allowedCities; // 还没放置的矩形索引
    private double[][] delta; // 信息素变化矩阵
    private double alpha;
    private double beta;

    private int squareNum; // 矩形数量
    private int firstSquare; // 第一个放置的矩形
    private int currentSquare; // 当前放置的矩形
    private double[][] different; // 不同度矩阵

    // 外矩形的长宽
    double L, W;
    Instance instance;

    Solution localSolution;

    //构造函数
    public Ant(int squareNum, List<Square> squareList, double L, double W, Instance instance) {
        this.squareNum = squareNum;
        this.squareList = squareList;
        this.L = L;
        this.W = W;
        this.instance = instance;
    }

    //初始化
    public void initAnt(double[][] different, double a, double b) {
        alpha = a;
        beta = b;
        placeSquareList = new ArrayList<>();
        // 初始允许搜索的矩形集合
        allowedCities = new ArrayList<>();
        // 初始禁忌表
        tabu = new ArrayList<>();
        // 初始相似度的倒数矩阵
        this.different = different;
        // 初始信息数变化矩阵为0
        delta = new double[squareNum][squareNum];
        for (int i = 0; i < squareNum; i++) {
            Integer integer = i;
            allowedCities.add(integer);
            for (int j = 0; j < squareNum; j++) {
                delta[i][j] = 0.f;
            }
        }
        // 设置起始矩形
        firstSquare = new Random(System.currentTimeMillis()).nextInt(squareNum); // 随机选取第一个矩形
        // 允许搜索的矩形集合中移除起始矩形
        for (Integer i : allowedCities) {
            if (i == firstSquare) {
                allowedCities.remove(i);
                break;
            }
        }
        // 将第一个放置的矩形添加至禁忌表
        tabu.add(firstSquare);
        // 第一个矩形即为当前放置的矩形
        currentSquare = firstSquare;
    }

    //选择下一个矩形
    public void selectNextSquare(double[][] pheromone) {
        double[] p = new double[squareNum];
        double sum = 0d;
        // 计算分母部分
        for (Integer i : allowedCities) {
            sum += Math.pow(pheromone[currentSquare][i], alpha)
                    * Math.pow(1.0 / different[currentSquare][i], beta);
        }
        // 计算概率矩阵
        for (int i = 0; i < squareNum; i++) {
            boolean flag = false;
            for (Integer j : allowedCities) {
                if (i == j) {
                    p[i] =  (Math.pow(pheromone[currentSquare][i], alpha) * Math
                            .pow(1.0 / different[currentSquare][i], beta)) / sum;
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                p[i] = 0.f;
            }
        }
        // 轮盘赌选择下一个矩形
        Random random = new Random(System.currentTimeMillis());
        double sleectP = random.nextDouble();
        int selectSquare = 0;
        double sum1 = 0d;
        for (int i = 0; i < squareNum; i++) {
            sum1 += p[i];
            if (sum1 >= sleectP) {
                selectSquare = i;
                break;
            }
        }
        // 从允许选择的矩形中去除select 矩形
        for (Integer i : allowedCities) {
            if (i == selectSquare) {
                allowedCities.remove(i);
                break;
            }
        }
        // 在禁忌表中添加select矩形
        tabu.add(selectSquare);
        currentSquare = selectSquare;
    }

    // 根据顺序进行装箱,并返回装载的矩形总面积
    public Solution calculateTotalPlaceSquareS() {
        // 根据顺序进行装箱
        return localSolution = packing(tabu.stream().map(index -> {
            return squareList.get(index);
        }).collect(Collectors.toList()));
    }

    // 按照顺序装载矩形
    private Solution packing(List<Square> squareList) {
        List<PlaceSquare> placeSquareList = new ArrayList<>();
        // 创建初始可放置角点
        List<PlacePoint> placePointList = new ArrayList<>();
        placePointList.add(new PlacePoint(0, 0, L));
        // 开始按照顺序和规则放置
        for (int i = 0; i < placePointList.size();) {
            PlacePoint placePoint = placePointList.get(i);
            double maxMark = -1.0d;
            int maxIndex = -1;
            double isRotate = -1, curMarks = -1;
            for (int j = 0; j < squareList.size(); j++) {
                Square square = squareList.get(j);
                double[] arr = getMarks(placePoint, square, placeSquareList);
                double is_rotate = arr[0];
                curMarks = arr[1];
                if (curMarks > 0 && curMarks > maxMark) {
                    maxMark = curMarks;
                    maxIndex = j;
                    isRotate = is_rotate;
                }
            }
            if (maxIndex < 0 && i < placePointList.size()) {
                i++;
            } else if (maxIndex < 0 && i >= placePointList.size()) {
                break;
            } else {
                Square square = squareList.remove(maxIndex);
                double l = square.getL();
                double w = square.getW();
                if (isRotate > 0) {
                    // 表示进行了旋转
                    square.setL(w);
                    square.setW(l);
                }
                // 移除当前角点
                placePointList.remove(i);
                //新增已放置的square
                placeSquareList.add(new PlaceSquare(placePoint.getX(), placePoint.getY(), square.getL(), square.getW()));
                // 新增两个可行角点
                double surplus = placePoint.getLen() - square.getL(); // 剩余长度
                if (surplus > 0) {
                    placePointList.add(new PlacePoint(placePoint.getX() + square.getL(), placePoint.getY(), surplus));
                }
                placePointList.add(new PlacePoint(placePoint.getX(), placePoint.getY() + square.getW(), square.getL()));
                // 重新排序
                Collections.sort(placePointList);
                i = 0;
                // 还原矩形
                if (isRotate > 0) {
                    // 表示进行了旋转
                    square.setL(l);
                    square.setW(w);
                }
            }
        }
        Solution solution = new Solution();
        solution.setInstance(instance);
        solution.setSquareList(new ArrayList<>(squareList));
        // 设置已经放置的矩形列表
        solution.setPlaceSquareList(new ArrayList<>(placeSquareList));
        // 计算利用率
        double rate = 0d;
        double s = 0d;
        for (PlaceSquare placeSquare : placeSquareList) {
            s += (placeSquare.getL() * placeSquare.getW());
        }
        rate = s / (L * W);
        solution.setRate(rate);
        return solution;
    }

    // 评价该点的得分
    private double[] getMarks(PlacePoint placePoint, Square square, List<PlaceSquare> placeSquareList) {
        // 返回{是否旋转,分数}
        double delta = 0, mark1 = -1d, mark2 = -1d;
        PlaceSquare placeSquare = new PlaceSquare(placePoint.getX(), placePoint.getY(), square.getL(), square.getW());
        if (isOverlap(placeSquareList, placeSquare)) {
            mark1 = -1.0d;
        } else {
            delta = Math.abs(placePoint.getLen() - square.getL());
            mark1 = 1 - delta / placePoint.getLen();
        }
        mark2 = -1.0d;
        if (instance.isRotateEnable()) {
            placeSquare = new PlaceSquare(placePoint.getX(), placePoint.getY(), square.getW(), square.getL());
            if (!isOverlap(placeSquareList, placeSquare)) {
                delta = Math.abs(placePoint.getLen() - square.getW());
                mark2 = 1 - delta / placePoint.getLen();
            }
        }
        if (mark1 >= mark2) {
            return new double[]{-1d, (int) (mark1 * 10)};
        }
        return new double[]{1d, (int) (mark2 * 10)};
    }

    // 判断放置在该位置是否超出边界或者和其他矩形重叠
    public boolean isOverlap(List<PlaceSquare> placeSquareList, PlaceSquare tempPlaceSquare) {
        // 出界
        if (tempPlaceSquare.getL() > L || tempPlaceSquare.getW() > W) {
            return true;
        }
        // 出界
        if (tempPlaceSquare.getX() + tempPlaceSquare.getL() > L || tempPlaceSquare.getY() + tempPlaceSquare.getW() > W) {
            return true;
        }
        for (PlaceSquare placeSquare : placeSquareList) {
            // 角点重合
            if (placeSquare.getX() == tempPlaceSquare.getX() && placeSquare.getY() == tempPlaceSquare.getY()) {
                placeSquareList.remove(placeSquare);
                return true;
            }
            // 判断即将要放置的块是否与之前放置的块有重叠
            if (isOverlap2(placeSquare, tempPlaceSquare)) {
                return true;
            }
        }
        return false;
    }

    // 判断即将要放置的块是否与之前放置的块有重叠
    public boolean isOverlap2(PlaceSquare placeSquare, PlaceSquare tempPlaceSquare) {
        double x1 = Math.max(placeSquare.getX(), tempPlaceSquare.getX());
        double y1 = Math.max(placeSquare.getY(), tempPlaceSquare.getY());
        double x2 = Math.min(placeSquare.getX() + placeSquare.getL(), tempPlaceSquare.getX() + tempPlaceSquare.getL());
        double y2 = Math.min(placeSquare.getY() + placeSquare.getW(), tempPlaceSquare.getY() + tempPlaceSquare.getW());
        if (x1 >= x2 || y1 >= y2) {
            return false;
        }
        return true;
    }

}

3.2 蚁群算法类 ACO_Packing

/**
 * @Author:WSKH
 * @ClassName:ACO_Packing
 * @ClassType:
 * @Description:
 * @Date:2022/5/29/14:50
 * @Email:1187560563@qq.com
 * @Blog:
 */
public class ACO_Packing {
    public Ant[] ants; // 蚂蚁
    public int antNum = 2; // 蚂蚁数量
    public int squareNum; // 矩形数量
    public int MAX_GEN = 20; // 最大迭代数
    public double[][] pheromone; // 信息素矩阵
    public double[][] different; // 不同度矩阵
    public double bestRate; // 最佳利用率
    public int[] bestSquence; // 最佳放置顺序
    public int bestT; //最佳迭代数
    public List<Square> squareList; // 矩形集合
    // 问题实例
    public Instance instance;
    // 大矩形(容器)的长宽
    double L, W;
    public Solution bestSolution; //最优解

    // 三个参数
    private double alpha = 1; //信息素重要程度
    private double beta = 5; //启发式因子重要程度
    private double rho = 0.5; //信息素挥发速率

    // 构造函数
    public ACO_Packing(Instance instance) {
        this.squareList = instance.getSquareList();
        this.instance = instance;
        this.L = instance.getL();
        this.W = instance.getW();
        squareNum = squareList.size();
        ants = new Ant[antNum];
    }

    // 外部调用窗口
    public Solution search() {
        long startTime = System.currentTimeMillis();
        initVar();
        bestSolution = solve();
        long endTime = System.currentTimeMillis();
        //求解完毕
        System.out.println("最佳迭代次数:"+bestT);
        System.out.println("最佳利用率为:"+bestSolution.getRate());
        System.out.println("程序运行了:" + (endTime - startTime) / 1000.0 + "秒");
        return bestSolution;
    }

    // 蚁群算法迭代求解
    public Solution solve() {
        // 迭代MAX_GEN次
        for (int g = 0; g < MAX_GEN; g++) {
            // antNum只蚂蚁
            for (int i = 0; i < antNum; i++) {
                // i这只蚂蚁走squareNum步,构成一个完整的矩形放置顺序
                for (int j = 1; j < squareNum; j++) {
                    ants[i].selectNextSquare(pheromone);
                }
                // 把这只蚂蚁初始放置矩形加入其禁忌表中
                ants[i].getTabu().add(ants[i].getFirstSquare());
                // 查看这只蚂蚁装载利用率是否比当前最优解优秀
                ants[i].calculateTotalPlaceSquareS();
                if (ants[i].getLocalSolution().getRate() > bestRate) {
                    // 比当前优秀则拷贝优秀的放置顺序
                    bestRate = ants[i].getLocalSolution().getRate();
                    for (int k = 0; k < squareNum; k++) {
                        bestSquence[k] = ants[i].getTabu().get(k);
                    }
                    bestT = g;
                    bestSolution = ants[i].getLocalSolution();
                    System.out.println("蚂蚁"+(i+1)+": 当前迭代次数为:"+g+",当前最佳利用率为:"+bestSolution.getRate());
                }
                // 更新这只蚂蚁的信息数变化矩阵,对称矩阵
                for (int j = 0; j < squareNum; j++) {
                    ants[i].getDelta()[ants[i].getTabu().get(j)][ants[i]
                            .getTabu().get(j + 1)] = (1.0 / ants[i]
                            .getLocalSolution().getRate());
                    ants[i].getDelta()[ants[i].getTabu().get(j + 1)][ants[i]
                            .getTabu().get(j)] = (1.0 / ants[i]
                            .getLocalSolution().getRate());
                }
            }
            // 更新信息素
            updatePheromone();
            // 重新初始化蚂蚁
            for (int i = 0; i < antNum; i++) {
                ants[i].initAnt(different, alpha, beta);
            }
        }
        // 返回结果
        return bestSolution;
    }

    //初始化
    public void initVar() {
        bestSolution = new Solution();
        //初始化不同度矩阵
        different = new double[squareNum][squareNum];
        for (int i = 0; i < squareNum; i++) {
            for (int j = 0; j < squareNum; j++) {
                if (i == j) {
                    different[i][j] = 0.0;
                } else {
                    different[i][j] = getDifferent(squareList.get(i), squareList.get(j));
                }
            }
        }
        //初始化信息素矩阵
        pheromone = new double[squareNum][squareNum];
        for (int i = 0; i < squareNum; i++) {
            for (int j = 0; j < squareNum; j++) {
                pheromone[i][j] = 0.1; // 初始化为0.1
            }
        }
        bestRate = 0d;
        bestSquence = new int[squareNum];
        // 放置蚂蚁
        for (int i = 0; i < antNum; i++) {
            ants[i] = new Ant(squareNum,squareList,L,W,instance);
            ants[i].initAnt(different, alpha, beta);
        }
    }

    // 更新信息素
    private void updatePheromone() {
        // 信息素挥发
        for (int i = 0; i < squareNum; i++) {
            for (int j = 0; j < squareNum; j++) {
                pheromone[i][j] = pheromone[i][j] * (1 - rho);
            }
        }
        // 信息素更新
        for (int i = 0; i < squareNum; i++) {
            for (int j = 0; j < squareNum; j++) {
                for (int k = 0; k < antNum; k++) {
                    pheromone[i][j] += ants[k].getDelta()[i][j];
                }
            }
        }
    }

    //计算矩形A对B的不同度
    public double getDifferent(Square A, Square B) {
        double different = 0d;
        different += Math.abs(A.getL()-B.getL())/B.getL();
        different += Math.abs(A.getW()-B.getW())/B.getW();
        return different;
    }

}

4 运行结果

由于测试的时候我的电脑在跑其他程序,比较卡顿,所以我仅设置了2只蚂蚁,和20次迭代,最终跑出来利用率为96.7%,大家迁移完代码之后记得要设置成合适的参数,否则可能会对解的质量会产生一定的影响。

二维装箱问题 java 二维装箱问题代码_蚁群算法_02


二维装箱问题 java 二维装箱问题代码_二维装箱问题 java_03

5 后话

本文仅列出了使用蚁群算法求解二维矩形装箱问题的2个核心类,如果需要整个跑通,还需要其他类,具体可以参照禁忌搜索算法求解二维矩形装箱问题(java代码实现)