【运筹优化】结合天际线启发式的遗传算法求解二维矩形装箱问题 + Java代码实现
创始人
2024-04-05 15:59:34

文章目录

  • 一、天际线启发式
  • 二、遗传算法结合天际线启发式
  • 三、Java代码实现
    • 3.1 项目结构
    • 3.2 Genome
    • 3.3 GA
    • 3.4 Run
    • 3.5 运行结果展示


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

一、天际线启发式

关于天际线启发式的介绍请看我的另一篇博客:【运筹优化】基于堆优化的天际线启发式算法和复杂的评分策略求解二维矩形装箱问题 + Java代码实现

二、遗传算法结合天际线启发式

遗传算法结合天际线启发式其实很简单,只需要将序列对应成一个矩形集合,传给天际线启发式算法进行求解即可。
也就是说,天际线启发式其实就是这类启发式算法的解码器(评价函数)。

三、Java代码实现

3.1 项目结构

红框以外的代码文件在上面说的博客里有,本博客只给 Run 、GA和Genome 的代码

在这里插入图片描述

3.2 Genome

/*** @Author:WSKH* @ClassName:Genome* @ClassType:* @Description:* @Date:2022/6/4/12:55* @Email:1187560563@qq.com* @Blog:https://blog.csdn.net/weixin_51545953?type=blog*/
public class Genome {// 边界的宽private double W;// 边界的高private double H;// 矩形数组private Item[] items;// 是否可以旋转private boolean isRotateEnable;//基因序列private int[] genomeArray;// 适应度函数值(在二维装箱问题中,其实就是装载利用率)private double fitness;// 序列对应的装载结果private Solution solution;/*** @param w 边界宽度* @param h 边界高度* @param items 矩形集合* @param isRotateEnable 是否可以旋转* @param genomeArray 基因序列* @Description 基因对象的构造函数*/public Genome(double w, double h, Item[] items, boolean isRotateEnable, int[] genomeArray) {W = w;H = h;this.items = items;this.isRotateEnable = isRotateEnable;this.genomeArray = genomeArray;}/*** @Description 获取适应值和路径长度*/public void updateFitnessAndSolution() {Item[] items = new Item[this.items.length];for (int i = 0; i < genomeArray.length; i++) {items[i] = this.items[genomeArray[i]];}solution = new SkyLinePacking(isRotateEnable, W, H, items).packing();fitness = solution.getRate();}public double getW() {return W;}public void setW(double w) {W = w;}public double getH() {return H;}public void setH(double h) {H = h;}public Item[] getItems() {return items;}public void setItems(Item[] items) {this.items = items;}public boolean isRotateEnable() {return isRotateEnable;}public void setRotateEnable(boolean rotateEnable) {isRotateEnable = rotateEnable;}public int[] getGenomeArray() {return genomeArray;}public void setGenomeArray(int[] genomeArray) {this.genomeArray = genomeArray;}public double getFitness() {return fitness;}public void setFitness(double fitness) {this.fitness = fitness;}public Solution getSolution() {return solution;}public void setSolution(Solution solution) {this.solution = solution;}
}

3.3 GA

/*** @Author:WSKH* @ClassName:GA* @ClassType:* @Description:* @Date:2022/6/4/12:55* @Email:1187560563@qq.com* @Blog:https://blog.csdn.net/weixin_51545953?type=blog*/
public class GA {// 最大的迭代次数private int MAX_GEN;// 种群数量信息private int popSize;// 变异对换次数private double variationExchangeCount;// 复制最优解的次数(选择种群的最优个体,然后复制几次,将最优个体复制多个,存到新的集合中)int cloneNumOfBestIndividual;// 基因突变概率private double mutationRate;// 基因交叉概率private double crossoverRate;// 种群总的适应度数值private double totalFitness;// 最好的适应度的对应的染色体(基因序列、路径序列)private Genome bestGenome;// 放置所有的种群基因信息private List population = new ArrayList<>();// 新一代的种群基因信息private List newPopulation = new ArrayList<>();// 遗传的代数(第几代)private int t = 0;// 最佳迭代次数private int bestT = -1;// 随机函数对象public Random random;// 各个个体的累积概率double[] probabilitys;// 矩形数量int itemNum;// 边界的宽private double W;// 边界的高private double H;// 矩形数组Item[] items;// 是否可以旋转private boolean isRotateEnable;/*** @param MAX_GEN                  迭代次数(提高这个值可以稳定地提高解质量,但是会增加求解时间)* @param popSize                  种群大小* @param variationExchangeCount   变异时的两两对换次数* @param cloneNumOfBestIndividual 复制最优解的次数(一般小于种群数量的5%,选择种群的最优个体,然后复制几次,将最优个体复制多个,存到新的集合中,有助于保持优良基因)* @param mutationRate             基因突变概率* @param crossoverRate            基因交叉概率* @param instance                 实例对象* @param seed                     随机数种子,如果传入null则不设置随机数种子,否则按照传入的种子进行设置,方便复现结果* @Description 遗传算法的构造函数*/public GA(int MAX_GEN, int popSize, int variationExchangeCount, int cloneNumOfBestIndividual, double mutationRate, double crossoverRate, Instance instance, Long seed) {this.MAX_GEN = MAX_GEN;this.popSize = popSize;this.variationExchangeCount = variationExchangeCount;this.cloneNumOfBestIndividual = cloneNumOfBestIndividual;this.mutationRate = mutationRate;this.crossoverRate = crossoverRate;this.W = instance.getW();this.H = instance.getH();this.isRotateEnable = instance.isRotateEnable();this.items = Item.copy(instance.getItemList().toArray(new Item[0]));this.random = seed == null ? new Random() : new Random(seed);}/*** @return 最佳装载结果对象Solution* @Description 遗传算法主函数*/public Solution solve() {initVar();while (t < MAX_GEN) {popSize = population.size();// 基因进化evolution();// 更新种群population = copyGenomeList(newPopulation);t++;}return bestGenome.getSolution();}/*** @Description 进化函数,正常交叉变异*/public void evolution() {//更新累积概率和总适应度updateProbabilityAndTotalFitness();// 挑选某代种群中适应度最高的个体selectBestGenomeAndJoinNext();// 赌轮选择策略对剩下的scale-1个个体进行随机的交叉,变异while (newPopulation.size() < population.size()) {double r = random.nextDouble();for (int j = 0; j < probabilitys.length; j++) {if (compareDouble(r, probabilitys[j]) != 1) {newPopulation.add(population.get(j));break;}}}// 交叉if (compareDouble(random.nextDouble(), crossoverRate) != 1) {int r = random.nextInt(popSize);int k = random.nextInt(popSize);while (r == k) {r = random.nextInt(popSize);}cross(k, r);}// 变异for (int i = 0; i < population.size(); i++) {if (compareDouble(random.nextDouble(), mutationRate) != 1) {variation(i);}}}/*** @param k1 基因1的索引* @param k2 基因2的索引* @Description 对基因1和基因2进行单点映射交叉*/private void cross(int k1, int k2) {// 获取要交叉的两个基因int[] genomeArray1 = newPopulation.get(k1).getGenomeArray();int[] genomeArray2 = newPopulation.get(k2).getGenomeArray();// 找到交叉位置int crossIndex = random.nextInt(itemNum);// 获取交叉片段for (int i = 0; i <= crossIndex; i++) {int temp = genomeArray1[i];genomeArray1[i] = genomeArray2[i];genomeArray2[i] = temp;}// 找到重复基因HashSet set = new HashSet<>();// HashMap repeatMap = new HashMap<>();for (int i = 0; i < genomeArray1.length; i++) {if (!set.add(genomeArray1[i])) {repeatMap.put(i, genomeArray1[i]);}}set.clear();for (int i = 0; i < genomeArray2.length; i++) {if (!set.add(genomeArray2[i])) {for (int key : repeatMap.keySet()) {genomeArray1[key] = genomeArray2[i];genomeArray2[i] = repeatMap.get(key);repeatMap.remove(key);break;}}}// 交叉完毕,将基因放回个体,再将个体放回种群,并更新他们的适应值和路径长度newPopulation.get(k1).setGenomeArray(genomeArray1);newPopulation.get(k1).updateFitnessAndSolution();newPopulation.get(k2).setGenomeArray(genomeArray2);newPopulation.get(k2).updateFitnessAndSolution();}/*** @param k 要进行变异的基因索引* @Description 变异(n次两两对换)*/private void variation(int k) {Genome genome = newPopulation.get(k);int[] genomeArray = genome.getGenomeArray();for (int i = 0; i < variationExchangeCount; i++) {int r1 = random.nextInt(itemNum);int r2 = random.nextInt(itemNum);while (r1 == r2) {r2 = random.nextInt(itemNum);}//交换int temp = genomeArray[r1];genomeArray[r1] = genomeArray[r2];genomeArray[r2] = temp;}//将变异后的基因序列放回个体genome.setGenomeArray(genomeArray);// 更新基因的适应值和路程长度genome.updateFitnessAndSolution();//将变异后的个体放回种群newPopulation.set(k, genome);}/*** @Description 初始化函数,进行一些初始化操作*/public void initVar() {this.itemNum = this.items.length;this.population = new ArrayList<>();//初始化种群信息List sequence = new ArrayList<>();for (int i = 0; i < itemNum; i++) {sequence.add(i);}for (int i = 0; i < popSize; i++) {Collections.shuffle(sequence);int[] initSequence = new int[itemNum];for (int j = 0; j < sequence.size(); j++) {initSequence[j] = sequence.get(j);}Genome genome = new Genome(W, H, items, isRotateEnable, initSequence.clone());genome.updateFitnessAndSolution();population.add(genome);}bestGenome = copyGenome(population.get(0));for (int i = 1; i < popSize; i++) {Genome genome = population.get(i);if (bestGenome.getFitness() < genome.getFitness()) {bestGenome = copyGenome(genome);}}System.out.println("初始解为:" + bestGenome.getSolution().getRate());}/*** @Description 挑选种群中适应度最大的个体基因,并复制n份,直接加入下一代种群*/public void selectBestGenomeAndJoinNext() {newPopulation = new ArrayList<>();Genome tempBest = population.get(0);for (int i = 1; i < population.size(); i++) {if (population.get(i).getFitness() > tempBest.getFitness()) {tempBest = population.get(i);}}if (compareDouble(tempBest.getFitness(), bestGenome.getFitness()) == 1) {bestGenome = copyGenome(tempBest);bestT = t;System.out.println("当前代数: " + t + " : " + bestGenome.getSolution().getRate());}for (int i = 0; i < cloneNumOfBestIndividual; i++) {newPopulation.add(copyGenome(tempBest));}}/*** @Description 更新各个个体的累积概率和种群总适应度*/public void updateProbabilityAndTotalFitness() {probabilitys = new double[population.size()];totalFitness = 0;for (Genome genome : population) {totalFitness += genome.getFitness();}double rate = 0.0;for (int i = 0; i < population.size(); i++) {rate += (population.get(i).getFitness() / totalFitness);probabilitys[i] = rate;}}/*** @param genome 模板基因* @return 返回拷贝好的基因* @Description 根据传入的模板基因,进行拷贝,返回拷贝好的基因*/public Genome copyGenome(Genome genome) {Genome copy = new Genome(genome.getW(), genome.getH(), genome.getItems(), genome.isRotateEnable(), genome.getGenomeArray().clone());copy.setSolution(genome.getSolution());copy.setFitness(genome.getFitness());return copy;}/*** @param genomeList 模板基因集合* @return 返回拷贝好的基因集合* @Description 根据传入的模板基因集合,进行拷贝,返回拷贝好的基因集合*/public List copyGenomeList(List genomeList) {List copyList = new ArrayList<>();for (Genome genome : genomeList) {copyList.add(copyGenome(genome));}return copyList;}/*** @param d1 双精度浮点型变量1* @param d2 双精度浮点型变量2* @return 返回0代表两个数相等,返回1代表前者大于后者,返回-1代表前者小于后者,* @Description 判断两个双精度浮点型变量的大小关系*/private int compareDouble(double d1, double d2) {// 定义一个误差范围,如果两个数相差小于这个误差,则认为他们是相等的 1e-06 = 0.000001double error = 1e-06;if (Math.abs(d1 - d2) < error) {return 0;} else if (d1 < d2) {return -1;} else if (d1 > d2) {return 1;} else {throw new RuntimeException("d1 = " + d1 + " , d2 = " + d2);}}}

3.4 Run

/*** @Author:WSKH* @ClassName:Run* @ClassType:* @Description:运行程序的主类* @Date:2022/11/6/19:39* @Email:1187560563@qq.com* @Blog:https://blog.csdn.net/weixin_51545953?type=blog*/
public class Run extends javafx.application.Application {private int counter = 0;@Overridepublic void start(Stage primaryStage) throws Exception {// 数据地址String path = "src/main/java/com/wskh/data/data.txt";// 根据txt文件获取实例对象Instance instance = new ReadDataUtil().getInstance(path);// 记录算法开始时间long startTime = System.currentTimeMillis();// 实例化遗传算法对象GA ga = new GA(1000, 300, 1, 10, 0.6, 0.98, instance, null);// 调用遗传算法对象进行求解Solution solution = ga.solve();// 输出相关信息System.out.println("------------------------------------------------------------------------------------");System.out.println("求解用时:" + (System.currentTimeMillis() - startTime) / 1000.0 + " s");System.out.println("共放置了矩形" + solution.getPlaceItemList().size() + "个");System.out.println("最佳利用率为:" + solution.getRate());// 输出画图数据String[] strings1 = new String[solution.getPlaceItemList().size()];String[] strings2 = new String[solution.getPlaceItemList().size()];for (int i = 0; i < solution.getPlaceItemList().size(); i++) {PlaceItem placeItem = solution.getPlaceItemList().get(i);strings1[i] = "{x:" + placeItem.getX() + ",y:" + placeItem.getY() + ",l:" + placeItem.getH() + ",w:" + placeItem.getW() + "}";strings2[i] = placeItem.isRotate() ? "1" : "0";}System.out.println("data:" + Arrays.toString(strings1) + ",");System.out.println("isRotate:" + Arrays.toString(strings2) + ",");// --------------------------------- 后面这些都是画图相关的代码,可以不用管 ---------------------------------------------AnchorPane pane = new AnchorPane();Canvas canvas = new Canvas(instance.getW(), instance.getH());pane.getChildren().add(canvas);canvas.relocate(100, 100);// 绘制最外层的矩形canvas = draw(canvas, 0, 0, instance.getW(), instance.getH(), true);// 添加按钮Button nextButton = new Button("Next +1");Canvas finalCanvas = canvas;nextButton.setOnAction(new EventHandler() {@Overridepublic void handle(ActionEvent actionEvent) {try {PlaceItem placeItem = solution.getPlaceItemList().get(counter);draw(finalCanvas, placeItem.getX(), placeItem.getY(), placeItem.getW(), placeItem.getH(), false);counter++;} catch (Exception e) {Alert alert = new Alert(Alert.AlertType.WARNING);alert.setContentText("已经没有可以放置的矩形了!");alert.showAndWait();}}});//pane.getChildren().add(nextButton);primaryStage.setTitle("二维矩形装箱可视化");primaryStage.setScene(new Scene(pane, 1000, 1000, Color.AQUA));primaryStage.show();}private Canvas draw(Canvas canvas, double x, double y, double l, double w, boolean isBound) {GraphicsContext gc = canvas.getGraphicsContext2D();// 边框gc.setStroke(Color.BLACK);gc.setLineWidth(2);gc.strokeRect(x, y, l, w);// 填充if (!isBound) {gc.setFill(new Color(new Random().nextDouble(), new Random().nextDouble(), new Random().nextDouble(), new Random().nextDouble()));} else {gc.setFill(new Color(1, 1, 1, 1));}gc.fillRect(x, y, l, w);return canvas;}public static void main(String[] args) {launch(args);}}

3.5 运行结果展示

输出(最后两行是在前端画图用的,可以忽略):

初始解为:0.98811875
当前代数: 1 : 0.990175
当前代数: 2 : 0.99215625
当前代数: 24 : 0.99268125
当前代数: 25 : 0.9942
当前代数: 31 : 0.99455625
当前代数: 34 : 0.99494375
当前代数: 37 : 0.99585
当前代数: 702 : 0.9977375
当前代数: 710 : 0.9986375
------------------------------------------------------------------------------------
求解用时:17.21 s
共放置了矩形26个
最佳利用率为:0.9986375
data:[{x:0.0,y:0.0,l:100.0,w:113.0}, {x:113.0,y:0.0,l:100.0,w:108.0}, {x:283.0,y:0.0,l:29.0,w:117.0}, {x:221.0,y:0.0,l:51.0,w:62.0}, {x:283.0,y:29.0,l:40.0,w:117.0}, {x:221.0,y:51.0,l:60.0,w:62.0}, {x:283.0,y:69.0,l:80.0,w:117.0}, {x:0.0,y:100.0,l:85.0,w:113.0}, {x:113.0,y:100.0,l:21.0,w:108.0}, {x:221.0,y:111.0,l:38.0,w:62.0}, {x:113.0,y:121.0,l:64.0,w:107.0}, {x:220.0,y:149.0,l:49.0,w:63.0}, {x:283.0,y:149.0,l:71.0,w:117.0}, {x:0.0,y:185.0,l:116.0,w:113.0}, {x:113.0,y:185.0,l:117.0,w:107.0}, {x:220.0,y:198.0,l:65.0,w:63.0}, {x:321.0,y:220.0,l:118.0,w:79.0}, {x:283.0,y:220.0,l:71.0,w:38.0}, {x:220.0,y:263.0,l:41.0,w:63.0}, {x:287.0,y:291.0,l:47.0,w:34.0}, {x:0.0,y:301.0,l:99.0,w:26.0}, {x:26.0,y:301.0,l:99.0,w:79.0}, {x:105.0,y:302.0,l:98.0,w:96.0}, {x:201.0,y:304.0,l:96.0,w:84.0}, {x:285.0,y:338.0,l:61.0,w:24.0}, {x:309.0,y:338.0,l:62.0,w:91.0}],
isRotate:[0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0],

可视化展示(利用率 99.86%)

顺便说一下,这个结果是我偶然跑出来的,后面再跑,跑了好久才复现出来,能找到这个结果也是偶然,不一定是算法强~

在这里插入图片描述

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...