一、代码
鱼类
package com.dam.heuristic.afas.test; import java.io.Serializable; import java.util.Random; /** * 人工鱼 */ public class Fish implements Serializable { //记录位置的维度(x,y,z,……) private int dimension; //记录位置 private double[] position; //鱼的适应度 private double fitness; //位置每个维度坐标的区间 private double[][] axisScopeArr; //目标函数是否为取最大值 private boolean isGetMax; public Fish(int dimension, double[][] axisScopeArr, boolean isGetMax) { this.dimension = dimension; this.position = new double[this.dimension]; this.axisScopeArr = axisScopeArr; this.isGetMax = isGetMax; this.fitness = 0; } /** * 初始化鱼的位置及适应度 */ public void initFishMessage() { Random random = new Random(); //初始化位置 for (int i = 0; i < this.dimension; i++) { position[i] = random.nextDouble() * (this.axisScopeArr[i][1] - this.axisScopeArr[i][0]) + this.axisScopeArr[i][0]; } //初始化适应度 this.fitness = new ObjectFunction().objectFunction(position,this.isGetMax); } public int getDimension() { return dimension; } public void setDimension(int dimension) { this.dimension = dimension; } public double[] getPosition() { return position; } public void setPosition(double[] position) { this.position = position; } public double getFitness() { return fitness; } public void setFitness(double fitness) { this.fitness = fitness; } }
目标函数类
package com.dam.heuristic.afas.test; public class ObjectFunction { /** * 目标函数 * 由于所举例子的目标函数的目标是最小化,适应度则是越大越好,因此这里需要取负值 * * @param position * @param isGetMax 是否获取函数的最大值 * @return */ public double objectFunction(double[] position, boolean isGetMax) { //目标:在变量区间范围最小化 y=x^2+y^2-xy-10x-4y+60 double value = Math.pow(position[0], 2) + Math.pow(position[1], 2) - position[0] * position[1] - 10 * position[0] - 4 * position[1] + 60; if (isGetMax == false) { return -value; } else { return value; } } }
算法类
package com.dam.heuristic.afas.test; import java.io.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; public class AfasApi { //鱼群中鱼的数量 private int fishNum; //尝试次数,该数越大,鱼的觅食能力越强,收敛速度越快 private int tryNum; //维度 private int dimension; //鱼的移动步长 private double stepLen; //拥挤度因子(delta=1/(random.nextDouble()*maxFishNum):maxFishNum为鱼的视野范围内最大的鱼的总数量) //当delta越大时,拥挤程度越小,鱼更难聚在一起,因此容易跳出局部最优,但是也会让其他鱼不想过来,造成找到的解不能精确逼近最优解 private double delta; //鱼的视野范围 private double visual; //迭代次数 private int genNum; //目标函数是否为取最大值 private boolean isGetMax; //位置每个维度坐标的区间 private double[][] axisScopeArr; public AfasApi(int fishNum, int tryNum, double stepLen, double delta, double visual, int genNum, boolean isGetMax, double[][] axisScopeArr) { this.fishNum = fishNum; this.tryNum = tryNum; this.dimension = axisScopeArr[0].length; this.stepLen = stepLen; this.delta = delta; this.visual = visual; this.genNum = genNum; this.isGetMax = isGetMax; this.axisScopeArr = axisScopeArr; } public double[][][] solve() { 变量声明 long start = System.currentTimeMillis(); //鱼群 Fish[] fishArr; //最好的鱼 Fish bestFish; //存储觅食、聚群、追尾所找到的最优鱼 Fish[] nextFishArr; //随机数工具类 Random random = new Random(); //目标函数类 ObjectFunction objectFunction = new ObjectFunction(); //存储每一代鱼所在位置 double[][][] positionArr = new double[this.genNum][this.fishNum][this.dimension]; 初始化鱼群 //初始化最优鱼 bestFish = new Fish(this.dimension, this.axisScopeArr, isGetMax); //初始化最优鱼的适应度为很小的值,说明当前最优鱼不咋的 bestFish.setFitness(-Double.MAX_VALUE); fishArr = new Fish[this.fishNum]; for (int i = 0; i < this.fishNum; i++) { //创建鱼 fishArr[i] = new Fish(this.dimension, this.axisScopeArr, isGetMax); //初始化鱼的位置及适应度 fishArr[i].initFishMessage(); //更新初始最优鱼 if (fishArr[i].getFitness() > bestFish.getFitness()) { bestFish = (Fish) deepClone(fishArr[i]); } } //初始化(觅食、聚群、追尾)所找到的最优鱼数组,三种方式 nextFishArr = new Fish[3]; 求解 for (int t = 0; t < this.genNum; t++) { for (int i = 0; i < this.fishNum; i++) { // System.out.println("找位置--------------------------------------------------------------------------"); //自己出门觅觅食,万一找到什么好地方就爽了 this.foraging(fishArr, nextFishArr, i, random, objectFunction, 0); //找到第i条鱼视野范围内的小鱼群 List<Fish> fishNearingFishIList = this.findFishArrNearFishI(i, fishArr); //如果鱼群的中间吃的比较多,又不太挤,就跟着去看看 this.cluster(fishArr, nextFishArr, i, fishNearingFishIList, random, objectFunction); //看一下哪条小鱼仔吃得比较好,旁边的地方又不太挤,就跟着去看看 this.follow(fishArr, nextFishArr, i, fishNearingFishIList, random, objectFunction); //汇总一下信息,根据所找到的位置选择出最好的位置,再决定去不去 this.upDateFishPositionAndBestFish(fishArr, nextFishArr, i, bestFish); positionArr[t][i] = fishArr[i].getPosition(); } } //如果函数求的是最小值,要取负值,因为算法是根据将目标函数取负号并以目标为求最大值来进行求解 double fitness = this.isGetMax == false ? -bestFish.getFitness() : bestFish.getFitness(); System.out.println("最优目标函数值:" + String.format("%.6f", fitness)); System.out.println("最优位置:" + Arrays.toString(bestFish.getPosition())); System.out.println("求解时间:" + (System.currentTimeMillis() - start) + "ms"); return positionArr; } /** * 觅食 * * @param index 由于聚群,追尾时找不到好位置的话,也要觅食,用index来控制所找位置的填充参数 */ private void foraging(Fish[] fishArr, Fish[] nextFishArr, int i, Random random, ObjectFunction objectFunction, int index) { //该鱼的下一可能位置 double[] nextPositionOfThisFish = new double[this.dimension]; //存储所寻找的最优位置 Fish bestPositionOfThisFish = new Fish(this.dimension, this.axisScopeArr, isGetMax); bestPositionOfThisFish.setFitness(-Double.MAX_VALUE); // 进行this.tryNum次尝试觅食,选择一个比较好的位置 for (int j = 0; j < this.tryNum; j++) { //让鱼在视野范围内随意走动一下 for (int k = 0; k < this.dimension; k++) { nextPositionOfThisFish[k] = fishArr[i].getPosition()[k] + (2 * (random.nextDouble()) - 1) * this.visual; } //修正一下位置 this.fixPosition(nextPositionOfThisFish, this.axisScopeArr); //计算一下所走动到的位置的食物如何,即适应度如何 double nextPositionFitness = objectFunction.objectFunction(nextPositionOfThisFish, this.isGetMax); if (nextPositionFitness > fishArr[i].getFitness()) { double distance = this.getDistanceOfTwoPositions(fishArr[i].getPosition(), nextPositionOfThisFish); for (int k = 0; k < this.dimension; k++) { //fishArr[i].getPosition()[k]:鱼的当前坐标 //(nextPositionOfThisFish.getPosition()[k] - fishArr[i].getPosition()[k]) / distance:该方向走的步长/两位置距离(可以结合直角三角形的投影关系进行理解) //this.stepLen * random.nextDouble():虽然一步可以走这么远,但是鱼不一定要竭尽全力地游,* random.nextDouble()可以让鱼在自己的大长腿范围内随机走一段距离 bestPositionOfThisFish.getPosition()[k] = fishArr[i].getPosition()[k] + ((nextPositionOfThisFish[k] - fishArr[i].getPosition()[k]) / distance) * this.stepLen * random.nextDouble(); } //设置新位置的适应度 bestPositionOfThisFish.setFitness(objectFunction.objectFunction(bestPositionOfThisFish.getPosition(), this.isGetMax)); } } //存储觅食找到的最优位置 nextFishArr[index] = bestPositionOfThisFish; // System.out.println("通过觅食找到更优位置" + bestPositionOfThisFish.getFitness()); } /** * 聚群 * * @param fishArr * @param nextFishArr * @param i * @param random * @param objectFunction */ private void cluster(Fish[] fishArr, Fish[] nextFishArr, int i, List<Fish> fishNearingFishIList, Random random, ObjectFunction objectFunction) { //存储所寻找的最优位置 Fish bestPositionOfThisFish = new Fish(this.dimension, this.axisScopeArr, this.isGetMax); bestPositionOfThisFish.setFitness(-Double.MAX_VALUE); //鱼群的中心位置 double[] centerPosition = new double[this.dimension]; for (int k = 0; k < this.dimension; k++) { centerPosition[k] = 0; } if (fishNearingFishIList.size() > 0) { //计算小鱼群的中心位置 for (int j = 0; j < fishNearingFishIList.size(); j++) { for (int k = 0; k < this.dimension; ++k) { centerPosition[k] += fishNearingFishIList.get(j).getPosition()[k]; } } //人工鱼的中心位置(各维度位置的平均值) for (int k = 0; k < this.dimension; k++) { centerPosition[k] /= fishNearingFishIList.size(); } //修正一下位置 this.fixPosition(centerPosition, this.axisScopeArr); //求中心位置的适应度 double centerPositionFitness = objectFunction.objectFunction(centerPosition, this.isGetMax); //求中心位置离第i条鱼的距离 double distance = this.getDistanceOfTwoPositions(fishArr[i].getPosition(), centerPosition); //centerPositionFitness / fishNearingFishIList.size() > this.delta * fishArr[i].getFitness():看一下要去的地方性价比怎么样 //就算那里伙食很好,住宿条件不太行(鱼太多,贼拥挤),小鱼鱼也懒得去 if (centerPositionFitness > fishArr[i].getFitness() && centerPositionFitness / fishNearingFishIList.size() > this.delta * fishArr[i].getFitness()) { for (int k = 0; k < this.dimension; k++) { bestPositionOfThisFish.getPosition()[k] = fishArr[i].getPosition()[k] + ((centerPosition[k] - fishArr[i].getPosition()[k]) / distance) * this.stepLen * random.nextDouble(); } //设置新位置的适应度 bestPositionOfThisFish.setFitness(objectFunction.objectFunction(bestPositionOfThisFish.getPosition(), this.isGetMax)); //存储聚群找到的最优位置 nextFishArr[1] = bestPositionOfThisFish; // System.out.println("通过聚群找到更优位置" + bestPositionOfThisFish.getFitness()); } else { //找到的中心位置不咋地,不如自己出去觅食 this.foraging(fishArr, nextFishArr, i, random, objectFunction, 1); } } else { //附近找不到鱼群,只能继续觅食了 this.foraging(fishArr, nextFishArr, i, random, objectFunction, 1); } } /** * 追尾行为 * * @param fishArr * @param nextFishArr * @param i * @param random * @param objectFunction * @throws IOException */ private void follow(Fish[] fishArr, Fish[] nextFishArr, int i, List<Fish> fishNearingFishIList, Random random, ObjectFunction objectFunction) { //存储所寻找的最优位置 Fish bestPositionOfThisFish = new Fish(this.dimension, this.axisScopeArr, isGetMax); bestPositionOfThisFish.setFitness(-Double.MAX_VALUE); if (fishNearingFishIList.size() > 0) { //存储最优邻居鱼 Fish bestNeighbourFish = new Fish(this.dimension, this.axisScopeArr, isGetMax); //存储最优邻居鱼对应的索引 int bestNeighbourFishIndex = i; //找邻居中适应度最高的那条鱼 for (int j = 0; j < fishNearingFishIList.size(); j++) { if (fishNearingFishIList.get(j).getFitness() > bestNeighbourFish.getFitness()) { bestNeighbourFish.setPosition(fishNearingFishIList.get(j).getPosition().clone()); bestNeighbourFishIndex = j; } } //修正一下坐标 this.fixPosition(bestNeighbourFish.getPosition(), this.axisScopeArr); if (bestNeighbourFish.getFitness() < fishArr[i].getFitness()) { //邻居鱼不咋地,还是自己去觅食吧 this.foraging(fishArr, nextFishArr, i, random, objectFunction, 2); } else { //找到最优邻居鱼旁边的鱼群 List<Fish> fishArrNearBestNeighbourFish = this.findFishArrNearFishI(bestNeighbourFishIndex, fishArr); if (fishArrNearBestNeighbourFish.size() > 0 && bestNeighbourFish.getFitness() / fishArrNearBestNeighbourFish.size() > this.delta * fishArr[i].getFitness()) { //求第i条鱼和最优邻居鱼之间的距离 double distance = this.getDistanceOfTwoPositions(fishArr[i].getPosition(), bestNeighbourFish.getPosition()); for (int k = 0; k < this.dimension; k++) { bestPositionOfThisFish.getPosition()[k] = fishArr[i].getPosition()[k] + ((bestNeighbourFish.getPosition()[k] - fishArr[i].getPosition()[k]) / distance) * this.stepLen * random.nextDouble(); } //设置新位置的适应度 bestPositionOfThisFish.setFitness(objectFunction.objectFunction(bestPositionOfThisFish.getPosition(), this.isGetMax)); //存储追尾找到的最优位置 nextFishArr[2] = bestPositionOfThisFish; // System.out.println("通过追尾找到更优位置" + bestPositionOfThisFish.getFitness()); } else { this.foraging(fishArr, nextFishArr, i, random, objectFunction, 2); } } } else { this.foraging(fishArr, nextFishArr, i, random, objectFunction, 2); } } /** * 更新第i条鱼的位置,同时更新最优的鱼 * * @param fishArr * @param nextFishArr * @param i * @param bestFish */ private void upDateFishPositionAndBestFish(Fish[] fishArr, Fish[] nextFishArr, int i, Fish bestFish) { for (int j = 0; j < nextFishArr.length; j++) { if (nextFishArr[j].getFitness() > fishArr[i].getFitness()) { fishArr[i].setPosition(nextFishArr[j].getPosition()); fishArr[i].setFitness(nextFishArr[j].getFitness()); } if (nextFishArr[j].getFitness() > bestFish.getFitness()) { // System.out.println("更新最优解"); bestFish.setPosition(nextFishArr[j].getPosition()); bestFish.setFitness(nextFishArr[j].getFitness()); } } } /** * 找一下附近有哪些鱼鱼 * * @param i * @return */ private List<Fish> findFishArrNearFishI(int i, Fish[] fishArr) { List<Fish> fishNearingFishIList = new ArrayList<>(); for (int j = 0; j < this.fishNum; j++) { if (this.getDistanceOfTwoPositions(fishArr[i].getPosition(), fishArr[j].getPosition()) < this.visual) { fishNearingFishIList.add(fishArr[j]); } } return fishNearingFishIList; } /** * 获取两个位置之间的距离 * * @param position1 * @param position2 */ private double getDistanceOfTwoPositions(double[] position1, double[] position2) { double distance = 0; for (int i = 0; i < position1.length; i++) { distance += Math.pow((position1[i] - position2[i]), 2); } return Math.sqrt(distance); } /** * 对位置进行限制,限制再变量区间内 * * @param position */ private void fixPosition(double[] position, double[][] axisScopeArr) { for (int i = 0; i < position.length; i++) { //让坐标处于区间内 if (position[i] > axisScopeArr[i][1]) { position[i] = axisScopeArr[i][1]; } else if (position[i] < axisScopeArr[i][0]) { position[i] = axisScopeArr[i][0]; } } } /** * 深拷贝对象 * * @param srcObject * @return */ private Object deepClone(Object srcObject) { ByteArrayOutputStream bos = null; ObjectOutputStream oos = null; ByteArrayInputStream bis = null; ObjectInputStream ois = null; Object result = null; try { bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); //将对象写到流里 oos.writeObject(srcObject); //从流里读回来 bis = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bis); result = ois.readObject(); } catch (Exception e) { e.printStackTrace(); } finally { try { bos.close(); oos.close(); bis.close(); ois.close(); } catch (IOException e) { e.printStackTrace(); } } return result; } }
二、测试
package com.dam.heuristic.afas.test; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFCell; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class AfasMainRun { public static void main(String[] args) { //变量的区间 double[][] axisScopeArr = { {-1000, 1000}, {-1000, 1000} }; AfasApi afasApi = new AfasApi(50, 50, 5, 0.2, 5, 1000, false, axisScopeArr); double[][][] position = afasApi.solve(); //创建WorkBook Workbook workbook = new XSSFWorkbook(); Sheet xData = workbook.createSheet("xData"); Sheet yData = workbook.createSheet("yData"); //获取每一代,每一个粒子的x坐标 CellStyle cellStyle = workbook.createCellStyle(); //设置居中 cellStyle.setAlignment(XSSFCellStyle.ALIGN_CENTER); for (int i = 0; i < position.length; i++) { Row xRow = xData.createRow(i); Row yRow = yData.createRow(i); for (int j = 0; j < position[0].length; j++) { Cell xCell = xRow.createCell(j); xCell.setCellValue(position[i][j][0]); //设置数据类型 xCell.setCellType(XSSFCell.CELL_TYPE_NUMERIC); //设置居中 xCell.setCellStyle(cellStyle); Cell yCell = yRow.createCell(j); yCell.setCellValue(position[i][j][1]); //设置数据类型 yCell.setCellType(XSSFCell.CELL_TYPE_NUMERIC); //设置居中 yCell.setCellStyle(cellStyle); } } try { FileOutputStream fileOutputStream = new FileOutputStream(new File("D:\\Desktop\\paintData.xlsx")); workbook.write(fileOutputStream); fileOutputStream.close(); System.out.println("存储文件完成"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
最优目标函数值:8.000012 最优位置:[8.00392710826662, 6.001480250477476] 求解时间:283ms
三、Matlab绘图
具体绘图方法可以参照文章数学建模常用算法:粒子群算法(PSO)求解二元函数最小值+限定x,y范围测试【java实现–详细注释+Matlab绘制粒子群飞行过程】