java实现遗传算法
序言
首先,网上的遗传算法的讲解很多,可以自行搜索参考(比较易懂的讲解),本文主要介绍java如何实现。
使用场景
遗传算法比较适用的场景一般是某一个命题有n多种解,或者说很难求出解,想在所有可能解中找出一个相对最优解。
实现思路
简单总结了五步:
-
首先,要针对命题一次性随机生成多组解,这些解组成初代种群,每个解就是个体
-
开始迭代,计算种群每一个解的舒适度,相当于按照某个规则打分,分数低的直接淘汰
-
未淘汰的个体进行最优几个保留(直接进入下一代),交叉(多次随机两个各取一部分形成新的个体),变异(几次随机取一个个体并随机修改个体的某一部分),形成新一代的种群,完成一次迭代
-
新种群再次进行迭代,回到第二步
-
当出现较优个体(满足预期分数值),或者到达最大迭代次数,停止迭代,返回最优个体
遗传算法
抽象
实现思路:由于个通用算法,可能适应各种场景,所以我把核心的代码封装成工具,对外暴露初始化种群,交叉变异的方法,就完成了抽象的遗传算法。
个体
我是这么设计的,每个解都是一个染色体(泛型T chromosome
),染色体的承载是个体(Individual
),个体承载染色体,同时包含一些例如舒适度的信息:
package com.pq.algorithm.ga.individual;
/**
* @Author pq
* @Date 2021/12/27 14:30
* @Description 个体
*/
public class Individual<T> {
// 染色体
private T chromosome;
// 舒适度
private double fitness;
// 舒适度在整个种群的占比=舒适度/种群舒适度之和*100 %
private double evolutionRate;
public void setFitness(double fitness) {
this.fitness = fitness;
}
public void setEvolutionRate(double evolutionRate) {
this.evolutionRate = evolutionRate;
}
public T getChromosome() {
return chromosome;
}
public double getFitness() {
return fitness;
}
public double getEvolutionRate() {
return evolutionRate;
}
public Individual(T chromosome) {
this.chromosome = chromosome;
}
}
个体工厂
个体的创建采用工厂模式,主要是留扩展,个体工厂代码:
package com.pq.algorithm.ga.individual;
/**
* @Author pq
* @Date 2022/1/17 11:14
* @Description 个体工厂
*/
public class IndividualFactory {
/**
* 生成一个新的个体
* @param <T>
* @return
*/
public static <T> Individual<T> init(T chromosome) {
Individual<T> individual = new Individual<>(chromosome);
return individual;
}
/**
* 从父母生成个体
* @param <T>
* @return
*/
public static <T> Individual<T> getFromParent(Individual<T> father, Individual<T> mother, T chromosome) {
Individual<T> individual = new Individual<>(chromosome);
return individual;
}
/**
* 从复制生成个体
* @param <T>
* @return
*/
public static <T> Individual<T> getFromCopy(Individual<T> mother) {
Individual<T> individual = new Individual<>(mother.getChromosome());
return individual;
}
/**
* 突变个体
* @param <T>
* @return
*/
public static <T> Individual<T> getFromMutation(Individual<T> mother, T chromosome) {
Individual<T> individual = new Individual<>(chromosome);
return individual;
}
}
设置
因为算法配置非常多,如迭代次数,个体数量等,所以单独做了个配置类来承载算法的所有配置,每个配置项有具体的注释:
package com.pq.algorithm.ga;
import com.pq.algorithm.ga.individual.Individual;
/**
* @Author pq
* @Date 2021/12/27 14:30
* @Description 基础设置
*/
public class Setting {
/**
* 染色体数量(可调整)
*/
private int chromosomeNum;
/**
* 迭代次数基数-最小迭代次数(可调整)
*/
private int generation = 100;
/**
* 迭代次数最大值(可调整)
*/
private int generationMax = 1000;
/**
* 复制的个数(计算属性)
*/
private int cpNum;
/**
* 突变概率
*/
private double mutationRate = 0.01;
/**
* 突变个数(计算属性)
*/
private int mutationNum;
/**
* 交叉变异染色体数量(计算属性)
*/
private int crossoverMutationNum;
/**
* 是否追踪每一代的舒适度
*/
private Boolean isTrace = false;
/**
* 最优变异:变异时会返回fixNum个舒适度最高的染色体,
* 当变异数量小于fixNum时,返回变异数量,当大于
* fixNum时,返回fixNum个,其余依然保持原
* 变异逻辑(针对交叉的结果进行变异)
* @see AbstractGeneticAlgorithm#fix(Individual[])
* 主要应用于定向优化,如再计算舒适度时记录较好染色体
* 影响舒适度的问题,变异时根据具体问题定向优化
*/
private int fixNum = 0;
/**
* 适应度,满足该适应度,放弃继续跌代提前返回
*/
private double expect = Double.MAX_VALUE;
/**
* 初始化
* @param chromosomeNum 个体个数
* @param cpRate 复制概率
*/
public Setting(int chromosomeNum, double cpRate) {
this.chromosomeNum = chromosomeNum;
this.cpNum = (int) (chromosomeNum * cpRate);
this.crossoverMutationNum = chromosomeNum - cpNum;
this.mutationNum = (int) (chromosomeNum * mutationRate);
}
/**
* 迭代次数设置
* @param min
* @param max
* @return this
*/
public Setting generation(int min, int max) {
this.generation = min;
this.generationMax = max;
return this;
}
/**
* 迭代次数设置
* @param val
* @return this
*/
public Setting generation(int val) {
this.generation = val;
this.generationMax = val;
return this;
}
public Setting fix(int fixNum) {
this.fixNum = fixNum;
return this;
}
/**
* 修改突变概率 不能超过10%
* @param mutationRate 突变几率
* @return this
*/
public Setting mutationRate(double mutationRate) {
if (mutationRate>0.1) {
mutationRate = 0.1;
}
this.mutationRate = mutationRate;
this.mutationNum = (int) (chromosomeNum * mutationRate);
return this;
}
/**
* 突变几率翻倍
*/
public void mutationRateIncrease() {
mutationRate(mutationRate*2);
}
/**
* 修改追踪
*/
public Setting trace() {
this.isTrace = true;
return this;
}
/**
* 修改期望值
* @param expect
*/
public Setting expect(double expect) {
this.expect = expect;
return this;
}
public int getChromosomeNum() {
return chromosomeNum;
}
public int getGeneration() {
return generation;
}
public int getGenerationMax() {
return generationMax;
}
public int getCpNum() {
return cpNum;
}
public int getMutationNum() {
return mutationNum;
}
public int getCrossoverMutationNum() {
return crossoverMutationNum;
}
public Boolean getTrace() {
return isTrace;
}
public double getExpect() {
return expect;
}
public int getFixNum() {
return fixNum;
}
}
最终解
结果包括:最终解,最终解舒适度,迭代次数,包装成为一个类:
package com.pq.algorithm.ga;
/**
* @Author pq
* @Date 2021/12/27 14:31
* @Description 结果
*/
public class Answer<T> {
/**
* 舒适度
*/
private double fitness;
/**
* 染色体
*/
private T solution;
/**
* 迭代次数
*/
private int generation;
public Answer(double fitness, T solution, int generation) {
this.fitness = fitness;
this.solution = solution;
this.generation = generation;
}
public double getFitness() {
return fitness;
}
public T getSolution() {
return solution;
}
public int getGeneration() {
return generation;
}
}
垃圾种群异常
有些是否初代种群或者某一代种群舒适度都是0,导致无法迭代,这时会抛出异常,重新随机初始化种群
package com.pq.algorithm.ga;
/**
* @Author pq
* @Date 2022/1/6 14:30
* @Description 垃圾种群
*/
public class BadPopulationException extends Exception {
public BadPopulationException() {
}
}
算法主类
以上都是对信息的封装和基本的配置,接下来就是核心代码,通过抽象的方式把初始化种群
,交叉
,计算舒适度
,变异
的实现暴露出去,方便使用者定制:
package com.pq.algorithm.ga;
import com.pq.algorithm.common.Timer;
import com.pq.algorithm.ga.individual.Individual;
import com.pq.algorithm.ga.individual.IndividualFactory;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Author pq
* @Date 2021/12/24 9:58
* @Description 遗传算法抽象类,继承需要实现几个接口
* @see #initChromosome() 初始化染色体
* @see #calfitness(Object) 计算染色体舒适度
* @see #crossover(Object, Object) 交叉
* @see #mutation(Object) 突变
* 追踪迭代情况,需要setting设置isTrace为true,{@link #search} 操作之后即可通过{@link #jsonWrite()}输出数据
* 并配合{@link ga.html}工具查看迭代情况
* @see Setting
* @see ga.html
* 使用例子请参照
* @see Example1 求x+y最大值 x,y取值范围[0-6]
*/
@SuppressWarnings("ALL")
@Slf4j
public abstract class AbstractGeneticAlgorithm<T> {
public AbstractGeneticAlgorithm(Setting setting) {
this.setting = setting;
population = new Individual[setting.getChromosomeNum()];
if (setting.getTrace()) {
traces = new ArrayList<>();
}
}
// 配置
private Setting setting;
//当前种群
private Individual<T>[] population;
//当前代
private int generation = 0;
// 追踪每次迭代的数据
private List<List<Double>> traces;
// 最终结果
private Answer<T> answer;
// 适应度不突破维持年代
private int stableFitnessGen = 0;
// 当前最优舒适度
private double bestFitness = 0;
// 多少不突破年代执行修正
private final int fixOnGen = 10;
/**
* 初始化一条染色体
* @return
*/
protected abstract T initChromosome();
/**
* 开始
*/
public Answer<T> search() {
initPopulation();
evolution();
return getAnswer();
}
/**
* 重置数据
*/
private void reset() {
population = new Individual[setting.getChromosomeNum()];
if (setting.getTrace()) {
traces = new ArrayList<>();
}
answer = null;
generation = 0;
bestFitness = 0;
stableFitnessGen = 0;
}
/**
* 初始化一个种群(10条染色体)
*/
private void initPopulation() {
for (int i = 0; i < setting.getChromosomeNum(); i++) {
this.population[i] = IndividualFactory.init(initChromosome());
}
}
/**
* 迭代进化
*/
private void evolution() {
try {
generation = 0;
// 计算适应度和概率
calFitnessAndRate();
for (int i = 0; i < setting.getGenerationMax(); i++) {
log.info("年代:{} 最优舒适度: {}", generation, bestFitness);
// 满足期待值可以提前退出
if (generation>=setting.getGeneration() && bestFitness >= setting.getExpect()) {
break;
}
// 年代超越初始设置年代或成为设置年代的倍数,基因突变概率增加
if (generation>0 && generation%setting.getGeneration()==0) {
setting.mutationRateIncrease();
}
// log.info("当前变异数: {}", setting.getMutationNum());
// 生成新种群
Individual<T>[] newPopulation = new Individual[setting.getChromosomeNum()];
// 交叉
Timer timer = new Timer();
crossover(newPopulation);
// log.info("交叉用时: {}", timer.count());
// 变异
mutation(newPopulation);
// log.info("变异用时: {}", timer.count());
// 复制
copy(newPopulation);
// log.info("复制用时: {}", timer.count());
// 修正
fix(newPopulation);
// log.info("修正用时: {}", timer.count());
// 替换当前种群
population = newPopulation;
// 更新代
generation = i+1;
// 计算适应度和概率
calFitnessAndRate();
// log.info("计算舒适度用时: {}", timer.count());
}
} catch (BadPopulationException e) {
reset();
// 重新初始化种群
initPopulation();
// 重新进化
evolution();
}
}
/**
* 计算舒适度
* @param chromosome
* @return
*/
protected abstract double calfitness(T chromosome);
/**
* 计算适应度和选择概率, 适应度越高生存概率越大
* @return
* @throws BadPopulationException 种群舒适度和为0
*/
public void calFitnessAndRate() throws BadPopulationException {
// 累计适应值总和
double totalFitness = 0;
// 最优舒适度
double currentBestFitness = 0;
for (int i = 0; i < setting.getChromosomeNum(); i++) {
double fitness = calfitness(population[i].getChromosome());
if (fitness<0) {
throw new RuntimeException("Please make sure that the fitness is greater than or equal to 0");
}
if (fitness>currentBestFitness) {
currentBestFitness = fitness;
}
if (setting.getTrace()) {
trace(generation, i, fitness);
}
population[i].setFitness(fitness);
// 所有染色体适应值总和
totalFitness = totalFitness + fitness;
// 计算最大舒适度
}
// 如果所有适应度都是0,重新开始
if (totalFitness==0) {
throw new BadPopulationException();
}
// 计算每个染色体被选中的概率
for (int i = 0; i < setting.getChromosomeNum(); i++) {
population[i].setEvolutionRate(population[i].getFitness()/totalFitness);
}
// 记录最大舒适度,以及维持年代
if (currentBestFitness<=bestFitness) {
if (currentBestFitness<bestFitness) {
log.warn("种群舒适度下滑:{}>>>{}", bestFitness, currentBestFitness);
}
stableFitnessGen++;
} else {
stableFitnessGen=0;
}
bestFitness = currentBestFitness;
}
/**
* 记录
* @param generation
* @param i
* @param fitness
*/
private void trace(int generation, int i, double fitness) {
if (traces.size() == generation) {
traces.add(new ArrayList<Double>());
}
traces.get(generation).add(fitness);
}
/**
* 轮盘赌算法
*/
private int rws() {
double sum = 0.0;
double r = Math.random();
// 模仿转盘转动
for (int i = 0; i < population.length; i++) {
sum += population[i].getEvolutionRate();
if (r <= sum) {
return i;
}
}
throw new RuntimeException("rws error");
}
/**
* 交叉操作,返回新的种群
*/
private void crossover(Individual<T>[] newPopulation) {
for (int i = 0; i < setting.getCrossoverMutationNum(); i++) {
Individual<T> father = population[rws()];
Individual<T> mother = population[rws()];
// 采用轮盘赌选择父母染色体
T fatherChromosome = father.getChromosome();
T motherChromosome = mother.getChromosome();
newPopulation[i] = IndividualFactory.getFromParent(father, mother, crossover(fatherChromosome, motherChromosome));
}
}
protected abstract T crossover(T father, T mother);
/**
* 基因突变操作
* @param newPopulation 新种群
*/
private void mutation(Individual<T>[] newPopulation) {
int mutationNum = setting.getMutationNum();
// 普通交叉变异
for (int i = 0; i< mutationNum; i++) {
int mutationIndex = (int) (Math.random() * setting.getCrossoverMutationNum());
T chromosome = mutation(newPopulation[mutationIndex].getChromosome());
newPopulation[mutationIndex] = IndividualFactory.getFromMutation(newPopulation[mutationIndex], chromosome);
}
}
/**
* 基因突变操作
*/
private void fix(Individual<T>[] newPopulation) {
if (stableFitnessGen<fixOnGen) {
return;
}
if (setting.getFixNum()<=0) {
return;
}
log.info("已持续{}代, 开始进行修正", stableFitnessGen);
// 折半避免调用过于频繁
stableFitnessGen = fixOnGen/2;
int fixNum = setting.getFixNum();
// 修正
for(int i=0; i<fixNum;i++) {
// 还是放入交叉位,避免把最优的变异错误,保留原最优解
int mutationIndex = (int) (Math.random() * setting.getCrossoverMutationNum());
Individual<T> tIndividual = newPopulation[newPopulation.length - 1 - i];
T chromosome = fix(tIndividual.getChromosome());
newPopulation[mutationIndex] = IndividualFactory.getFromMutation(tIndividual, chromosome);
}
}
/**
* 染色体突变
* @param chromosome
*/
protected abstract T mutation(T chromosome);
/**
* 修正
* @param index
* @param chromosome
* @return
*/
protected T fix(T chromosome) {
return chromosome;
}
/**
* 复制最优秀的n个染色体直接进入下一代
* @param newPopulation
*/
private void copy(Individual<T>[] newPopulation) {
Individual<T>[] cps = maxN(setting.getCpNum());
int i = setting.getCrossoverMutationNum();
for (Individual<T> cp : cps) {
Individual<T> mother = population[i];
newPopulation[i] = IndividualFactory.getFromCopy(mother);
i++;
}
}
/**
* 冒泡算法,取最优的n个染色体,排序从小到大
* @return
*/
private Individual<T>[] maxN(int n) {
for (int i=0; i<n; i++) {
for (int j=0; j<population.length-1-i; j++) {
if (population[j].getFitness() > population[j+1].getFitness()) {
Individual temp = population[j];
population[j] = population[j+1];
population[j+1] = temp;
}
}
}
Individual<T>[] individuals = Arrays.copyOfRange(population, population.length - n, population.length);
return individuals;
}
public List<List<Double>> getTraces() {
if (!setting.getTrace()) {
throw new IllegalArgumentException("Please set isTrace to true");
}
return traces;
}
/**
* 获取最优结果
* @return
*/
public Answer<T> getAnswer() {
if (answer==null) {
getAnswerFrompopulation();
}
return answer;
}
/**
* 获得最优个体
*/
private Individual<T> getBestIndividualFrompopulation() {
int bestIndex = 0;
double bestFitness = 0;
for (int i = 0; i < setting.getChromosomeNum(); i++) {
Individual<T> individual = population[i];
if (individual.getFitness()>bestFitness) {
bestFitness = individual.getFitness();
bestIndex = i;
}
}
return population[bestIndex];
}
/**
* 从当前种群获取最优解
*/
private void getAnswerFrompopulation() {
Individual<T> bestIndividual = getBestIndividualFrompopulation();
answer = new Answer(bestIndividual.getFitness(), bestIndividual.getChromosome(), generation);
}
}
实现
以上就是对遗传算法工具的封装,接下来尝试使用,写一个简单的例子。
我们的命题就是从0-6之间取两个浮点数x,y
,得到两数相加最大的一组解x,y
(我们都知道最优解肯定是6,6,但这里假装不知道,看遗传算法能不能帮算出来)
我们先写个Example
继承遗传算法类
public class Example extends AbstractGeneticAlgorithm<String> {
public Example(Setting setting) {
super(setting);
}
@Override
protected String initChromosome() {
return null;
}
@Override
protected double calfitness(String chromosome) {
return 0;
}
@Override
protected String crossover(String father, String mother) {
return null;
}
@Override
protected String mutation(String chromosome) {
return null;
}
}
需要实现的就是初始化染色体,交叉,变异,计算舒适度四个方法。
编解码
在实现方法之前,因为要后续要进行随机的交叉和变异,所以最好解进行编码,最简单就是类似0111000011这样,也就形成染色体,可以方便初始随机生成个体,后续进行交叉就可以两个各取一段,变异既可以随便取一段进行变化,比如交叉:
然后研究我们的命题,使用二进制编码,我们是一组解两个值,每个值是0-6之间的浮点数,我们把这个区间分成2的10次方=1024段,每段的连接点是一个解,所以一共有1024个解,表示为10位的二进制。
这个二进制的数怎么转换为0-6的浮点数呐,只要用这个二进制的值/1024-1*6既可以得到这个浮点数。
由于是两个值,可以用拼接形式,前半段是
x
,后半段是y
,总共20位二进制,代码如下
/**
* 将染色体解码成x,y变量的值
*/
private double[] decoding(String str) {
int a = Integer.parseInt(str.substring(0, 10), 2);
int b = Integer.parseInt(str.substring(10, 20), 2);
double x = a * (6.0 - 0) / (Math.pow(2, 10) - 1);
double y = b * (6.0 - 0) / (Math.pow(2, 10) - 1);
double[] returns = {x, y};
return returns;
}
如果传入“00000000000000000000”方法返回0,传入“11111111111111111111”方法返回6,传入其它返回0-6之间的浮点数。
初始化染色体
首选实现初始化染色体代码,代码会根据setting
中配置的chromosomeNum(染色体数量)
多次调用这个方法形成初始化种群,初始化染色体很简单,我们已经规定我们的染色体是20位的二进制,那就每一位随机0或1即可
@Override
protected String initChromosome() {
String res = "";
for (int i = 0; i < 20; i++) {
if (Math.random() > 0.5) {
res += "0";
} else {
res += "1";
}
}
return res;
}
计算舒适度
下一步实现计算舒适度,我们的命题很简单,两个值的和越大越好,所以就算两个值的和,越大舒适度越高
@Override
protected double calfitness(String chromosome) {
double[] xy = decoding(chromosome);
double fitness = xy[0] + xy[1];
return fitness;
}
交叉
交叉思路也很简单,一共20位,任选一位做分隔点,然后父母一个取前半段,一个取后半段,合并一起形成新的染色体
@Override
protected String crossover(String father, String mother) {
// 交叉点
int pos = (int) (Math.random() * 20) + 1;
// 生孩子
String son = father.substring(0, pos) + mother.substring(pos);
return son;
}
变异
变异也叫突变,任选一个位置,如果是0变成1,如果是1变成0
@Override
protected String mutation(String chromosome) {
// 突变点
int pos = (int) (Math.random() * (20-2)) + 1;
String a; //记录变异位点变异后的编码
if (chromosome.charAt(pos) == '0') {
a = "1";
} else {
a = "0";
}
return chromosome.substring(0, pos -1) + a
+ chromosome.substring(pos);
}
完成
到此所有该实现的方法就实现了,完整代码如下
package com.pq.algorithm.ga.example;
import com.pq.algorithm.ga.AbstractGeneticAlgorithm;
import com.pq.algorithm.ga.Setting;
/**
* @Author pq
* @Date 2022/3/15 15:18
* @Description 使用遗传算法 本题包含两个属性x, y,分别从[0-6]中选择,选举出x+y最大的最优解
*/
public class Example extends AbstractGeneticAlgorithm<String> {
public static final int genoNum = 20;
public Example(Setting setting) {
super(setting);
}
@Override
protected String initChromosome() {
String res = "";
for (int i = 0; i < genoNum; i++) {
if (Math.random() > 0.5) {
res += "0";
} else {
res += "1";
}
}
return res;
}
@Override
protected double calfitness(String chromosome) {
double[] xy = decoding(chromosome);
double fitness = xy[0] + xy[1];
return fitness;
}
@Override
protected String crossover(String father, String mother) {
// 交叉点
int pos = (int) (Math.random() * genoNum) + 1;
// 生孩子
String son = father.substring(0, pos) + mother.substring(pos);
return son;
}
@Override
protected String mutation(String chromosome) {
// 突变点
int pos = (int) (Math.random() * (genoNum-2)) + 1;
String a; //记录变异位点变异后的编码
if (chromosome.charAt(pos) == '0') {
a = "1";
} else {
a = "0";
}
return chromosome.substring(0, pos -1) + a
+ chromosome.substring(pos);
}
/**
* 将染色体解码成x,y变量的值
*/
private double[] decoding(String str) {
int a = Integer.parseInt(str.substring(0, genoNum/2), 2);
int b = Integer.parseInt(str.substring(genoNum/2, genoNum), 2);
double x = a * (6.0 - 0) / (Math.pow(2, 10) - 1);
double y = b * (6.0 - 0) / (Math.pow(2, 10) - 1);
double[] returns = {x, y};
return returns;
}
}
测试
接下来进行测试,写一个测试方法
public static void main(String args[]) {
Setting setting = new Setting(500, 0.2) // 500个染色体 复制率20%
.generation(100, 1000) // 最低迭代数100,最高1000
.mutationRate(0.01) // 突变概率
.expect(11.99); // 期待舒适度
Example ga = new Example(setting);
// 开始
Answer<String> answer = ga.search();
// 打印结果
double[] xy = ga.decoding(answer.getSolution());
String str = "最优解舒适度=" + answer.getFitness() + ", 年代="+answer.getGeneration()+",染色体:<" + answer.getSolution() + ">"
+ ", x=" + xy[0] + ", y=" + xy[1];
System.out.println(str);
}
最终打印结果
最优解舒适度=12.0, 年代=100,染色体:<11111111111111111111>, x=6.0, y=6.0
下面一些配置的解释
复制率
除了交叉变异,算法中新一代种群个体产生的方法还有复制,即取上一代几个(复制率决定)最优的解直接进入下一代
迭代数&期待舒适度
至少迭代最低迭代数
次,超过100次如果满足期待舒适度
则返回结果,否则一直迭代到最高迭代数
突变率
决定一个种群取多少个进行突变,100%-突变率-复制率=交叉率
跟踪
如果setting
如下设置.trace()
Setting setting = new Setting(500, 0.2) // 500个染色体 复制率20%
.trace(); // 开启跟踪
代表跟踪,主要是一个调试的作用,这样在完成算法执行后可以通过
ga.getTraces();
获取整个迭代过程的数据记录,包含每一代的每一个个体的舒适度,结构为
[
[3.9824046920821115, 7.055718475073315, 0.9794721407624634,...], //第一代每个个体舒适度
[3.9824046920821115, 7.055718475073315, 0.9794721407624634,...], //第二代每个个体舒适度
...
]
这个数据有什么用呐?可以给前端配合图标做一个散点图,就可以清晰的看到遗传算法是否达到预想效果:
遗传算法调试图
可以看到刚开始舒适度在0-12之间分步,后续迭代逐渐向12进化,越来越好,说明遗传迭代的比较好,当出现问题时候也可以通过这个图直观的看出来问题点。
应用
以上就是java实现遗传算法,并解决了一个小问题,从例子上看是废了很大劲解决了个6+6=12的问题,看似很傻,其实不然。
现实中有很多复杂的问题人脑是不能一下算出来的,通过遗传算法可以很方便的得到一个近似解。
比如我就用基于以上的遗传算法实现了一个相当复杂的排课的功能:
使用遗传算法实现排课
总结
总结一下遗传算法,其实可以简单概括为:对于一些很难求解的命题,一次性随机n组解,然后通过打分、淘汰、进化的手段最终得到一个近乎完美的解。
就像生物,物竞天择、适者生存,劣质的个体逐渐淘汰,剩下的生物越来越适应大自然。