算法系列-遗传算法和JAVA实现

遗传算法是由美国的J. Holland教授于1975年在他的专著《自然界和人工系统的适应性》中首先提出的。 借鉴生物界自然选择和自然遗传机制的随机化搜索算法。

基本概述

遗传算法的基本思想是通过对候选解进行编码,然后通过遗传操作(选择、交叉和变异)来生成新的解,并对解的质量进行评估和选择。这个过程模拟了自然界中的遗传和进化过程,不断迭代直到找到满足特定条件的最优解或接近最优解。 基本步骤:

  1. 初始化种群:随机生成一组初始的候选解,称为种群。
  2. 评估适应度:根据问题的特定评价函数,对每个个体计算其适应度值,该值表示个体解的好坏程度。
  3. 选择操作:根据适应度值,选择一部分个体作为父代,用于产生下一代个体。常用的选择策略有轮盘赌选择、锦标赛选择等。
  4. 交叉操作:从父代中选取一对个体,通过某种方式进行交叉操作,生成新的个体。交叉操作可以将两个个体的染色体部分进行交换、重组等操作。
  5. 变异操作:对新生成的个体进行变异操作,引入一定的随机性,以增加搜索空间的探索能力。
  6. 更新种群:将生成的新个体加入到种群中,替换掉部分旧个体。
  7. 判断终止条件:根据问题的要求,判断是否达到终止条件,例如达到最大迭代次数或找到满足要求的解等。
  8. 返回结果:返回找到的最优解或近似最优解作为算法的输出。

算法流程图如下

实际场景

下面是一个基于Java实现的遗传算法示例,假设背景是解决一个简单的二进制字符串匹配问题。

背景

假设我们有一个目标二进制字符串,例如"11011010"。我们的目标是使用遗传算法找到一个尽可能接近目标字符串的二进制字符串。每个候选解都是一个长度为8的二进制字符串。

实现思路

  1. 初始化种群:生成一个随机的种群,其中每个个体是一个长度为8的随机二进制字符串。
  2. 评估适应度:计算每个个体与目标字符串的匹配程度作为适应度值。匹配程度可以是相同位置上相同字符的数量。
  3. 选择操作:根据适应度值,使用轮盘赌选择算法选择一部分个体作为父代。
  4. 交叉操作:从父代中选择两个个体,通过单点交叉操作生成两个子代个体。
  5. 变异操作:对子代个体进行变异操作,以增加种群的多样性。可以通过随机选择某个位置上的位进行翻转。
  6. 更新种群:将生成的子代个体加入到种群中,替换掉部分旧个体。
  7. 判断终止条件:达到最大迭代次数或找到与目标字符串完全匹配的解。
  8. 返回结果:输出找到的最优解或接近最优解。

基于JAVA代码实现

代码实现

java 复制代码
public class GeneticAlgorithmExample {
    /**
     * 种群大小
     */
    private static final int POPULATION_SIZE = 100;
    /**
     * 每个个体染色体的长度
     */
    private static final int CHROMOSOME_LENGTH = 64;
    /**
     * 最大迭代次数
     */
    private static final int MAX_GENERATIONS = 10000;
    /**
     * 表示遗传算法变异率
     */
    private static final double MUTATION_RATE = 0.1;
    /**
     * 目标解
     */
    private static String TARGET_STRING = "11011010";

    public static void main(String[] args) {
        TARGET_STRING = initTargetStr();
        int[][] population = initializePopulation();
        int[] fitnessScores = calculateFitness(population);
        for (int generation = 1; generation <= MAX_GENERATIONS; generation++) {

            int[][] newPopulation = new int[POPULATION_SIZE][CHROMOSOME_LENGTH];
            System.out.println("Init " + "  Best fitness: " + getBestFitness(fitnessScores));
            for (int i = 0; i < POPULATION_SIZE; i++) {
                if (noEvolution(fitnessScores, i)) {
                    newPopulation[i] = population[i];
                    continue;
                }
                int[] parent1 = selectParent(population, fitnessScores);
                int[] parent2 = selectParent(population, fitnessScores);
                int[] offspring = crossover(parent1, parent2);
                offspring = mutate(offspring);
                newPopulation[i] = offspring;
            }

            population = newPopulation;
            fitnessScores = calculateFitness(population);
            System.out.println("Generation: " + generation + "  Best fitness: " + getBestFitness(fitnessScores));

            if (isSolutionFound(population)) {
                System.out.println("Solution found at generation: " + generation);
                break;
            }
        }

        fitnessScores = calculateFitness(population);
        int bestIndividualIndex = getBestIndividualIndex(fitnessScores);
        int[] bestIndividual = population[bestIndividualIndex];

        System.out.println("Best solution found: " + Arrays.toString(bestIndividual));
        System.out.println("Matched string: " + getStringFromBinary(bestIndividual));
    }

    //初始化字符串
    private static String initTargetStr() {
        StringBuilder stringBuilder = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < CHROMOSOME_LENGTH; i++) {
            stringBuilder.append(random.nextInt(2));
        }
        System.out.println("init target str" + stringBuilder);
        return stringBuilder.toString();
    }

    //判断是否需要进化,适合物竞天择理论
    private static boolean noEvolution(int[] fitnessScores, int index) {
        double avg = IntStream.of(fitnessScores).average().getAsDouble();
        return fitnessScores[index] > avg;
    }

    // 初始化种群
    private static int[][] initializePopulation() {
        int[][] population = new int[POPULATION_SIZE][CHROMOSOME_LENGTH];
        Random random = new Random();

        for (int i = 0; i < POPULATION_SIZE; i++) {
            for (int j = 0; j < CHROMOSOME_LENGTH; j++) {
                population[i][j] = random.nextInt(2);
            }
        }

        return population;
    }

    // 计算适应度值
    private static int[] calculateFitness(int[][] population) {
        int[] fitnessScores = new int[POPULATION_SIZE];

        for (int i = 0; i < POPULATION_SIZE; i++) {
            String chromosomeString = getStringFromBinary(population[i]);
            int matchingCount = 0;

            for (int j = 0; j < CHROMOSOME_LENGTH; j++) {
                if (chromosomeString.charAt(j) == TARGET_STRING.charAt(j)) {
                    matchingCount++;
                }
            }

            fitnessScores[i] = matchingCount;
        }

        return fitnessScores;
    }

    // 选择父代
    private static int[] selectParent(int[][] population, int[] fitnessScores) {
        int totalFitness = Arrays.stream(fitnessScores).sum();
        double randomFitness = totalFitness * Math.random();
        double cumulativeFitness = 0;

        for (int i = 0; i < POPULATION_SIZE; i++) {
            cumulativeFitness += fitnessScores[i];

            if (cumulativeFitness >= randomFitness) {
                return population[i];
            }
        }

        return population[POPULATION_SIZE - 1]; // Fallback, should not reach here
    }

    // 单点交叉操作
    private static int[] crossover(int[] parent1, int[] parent2) {
        int crossoverPoint = new Random().nextInt(CHROMOSOME_LENGTH);
        int[] offspring = new int[CHROMOSOME_LENGTH];

        for (int i = 0; i < CHROMOSOME_LENGTH; i++) {
            if (i < crossoverPoint) {
                offspring[i] = parent1[i];
            } else {
                offspring[i] = parent2[i];
            }
        }

        return offspring;
    }

    // 变异操作
    private static int[] mutate(int[] individual) {
        for (int i = 0; i < CHROMOSOME_LENGTH; i++) {
            if (Math.random() < MUTATION_RATE) {
                individual[i] = 1 - individual[i]; // Flip the bit
            }
        }

        return individual;
    }

    // 获取最佳适应度值
    private static int getBestFitness(int[] fitnessScores) {
        return Arrays.stream(fitnessScores).max().orElse(0);
    }

    // 获取最佳个体的索引值
    private static int getBestIndividualIndex(int[] fitnessScores) {
        int index = 0;
        int max = fitnessScores[0];
        for (int i = 0; i < fitnessScores.length; i++) {
            if (fitnessScores[i] > max) {
                max = fitnessScores[i];
                index = i;
            }
        }
        return index;
    }

    // 检查是否找到解决方案
    private static boolean isSolutionFound(int[][] population) {
        for (int i = 0; i < POPULATION_SIZE; i++) {
            String chromosomeString = getStringFromBinary(population[i]);
            if (chromosomeString.equals(TARGET_STRING)) {
                return true;
            }
        }
        return false;
    }

    // 将二进制数组转换为字符串
    private static String getStringFromBinary(int[] binaryArray) {
        StringBuilder stringBuilder = new StringBuilder();

        for (int bit : binaryArray) {
            stringBuilder.append(bit);
        }

        return stringBuilder.toString();
    }
}

执行结果

执行结果差距主要在于个体适应度上,主要是是否需要把适应度高的个体保留

java 复制代码
//判断是否需要进化,适合物竞天择理论
private static boolean noEvolution(int[] fitnessScores, int index) {
    double avg = IntStream.of(fitnessScores).average().getAsDouble();
    return fitnessScores[index] > avg;
}

以下是两种执行结果

  • 如果随机迭代,结果如下(注释调noEvolution相关逻辑)
yaml 复制代码
init target str1101001011001001000110001000000111000010000100110011101111111010
Init   Best fitness: 42
Generation: 1  Best fitness: 40
...
Generation: 10000  Best fitness: 44
Best solution found: [1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0]
Matched string: 1111001011100110100010011000101111100010000101100001011100110010
  • 不是随机迭代
yaml 复制代码
init target str0111101010001111111100000100111110011111011011001000101101111110
Init   Best fitness: 44
Generation: 1  Best fitness: 44
Init   Best fitness: 44
...
Generation: 896  Best fitness: 64
Solution found at generation: 896
Best solution found: [0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0]
Matched string: 0111101010001111111100000100111110011111011011001000101101111110

可以看出不到1000迭代就找到最优解,遗传算法主要需要适应自然法则,才能快速找到最优解。

参考

1.深入浅出遗传算法

2.【优化算法】 简述遗传算法(GA)原理

相关推荐
SharkWeek.2 分钟前
【力扣Hot 100】普通数组2
数据结构·算法·leetcode
Amd7946 小时前
深入探讨索引的创建与删除:提升数据库查询效率的关键技术
数据结构·sql·数据库管理·索引·性能提升·查询优化·数据检索
XianxinMao7 小时前
RLHF技术应用探析:从安全任务到高阶能力提升
人工智能·python·算法
hefaxiang7 小时前
【C++】函数重载
开发语言·c++·算法
exp_add38 小时前
Codeforces Round 1000 (Div. 2) A-C
c++·算法
查理零世9 小时前
【算法】经典博弈论问题——巴什博弈 python
开发语言·python·算法
神探阿航9 小时前
第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组
java·算法·蓝桥杯
皮肤科大白9 小时前
如何在data.table中处理缺失值
学习·算法·机器学习
不能只会打代码11 小时前
蓝桥杯例题一
算法·蓝桥杯
OKkankan11 小时前
实现二叉树_堆
c语言·数据结构·c++·算法