禅与计算机程序设计艺术算法Java

java实现遗传算法

2022-03-15  本文已影响0人  pq217

序言

首先,网上的遗传算法的讲解很多,可以自行搜索参考(比较易懂的讲解),本文主要介绍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组解,然后通过打分、淘汰、进化的手段最终得到一个近乎完美的解。
就像生物,物竞天择、适者生存,劣质的个体逐渐淘汰,剩下的生物越来越适应大自然。

上一篇下一篇

猜你喜欢

热点阅读