1. 概述

本系列文章旨在解释遗传算法的核心思想并展示最知名的实现方案。本文将详细介绍 Jenetics 这个强大的 Java 库,它可用于解决各类优化问题。

如果你觉得需要先巩固遗传算法基础知识,建议先阅读这篇入门文章

2. 核心原理

根据官方文档,Jenetics 是一个基于进化算法的 Java 库。进化算法源于生物学,其核心机制模拟了生物进化过程,包括:

  • ✅ 繁殖
  • ✅ 变异
  • ✅ 重组
  • ✅ 选择

Jenetics 完全基于 Java Stream 接口实现,能与标准 Stream API 无缝集成。

核心特性包括:

  • 无摩擦优化 - 无需修改适应度函数,只需调整 Engine 配置即可启动应用
  • 零依赖 - 运行时无需任何第三方库
  • Java 8 原生支持 - 完整支持 Stream 和 Lambda 表达式
  • 多线程支持 - 进化步骤可并行执行

使用前需添加 Maven 依赖:

<dependency>
    <groupId>io.jenetics</groupId>
    <artifactId>jenetics</artifactId>
    <version>3.7.0</version>
</dependency>

最新版本可在Maven 中央仓库获取。

3. 实战案例

我们将通过三个经典优化问题展示 Jenetics 的核心功能,从简单的二进制算法到复杂的背包问题。

3.1. 简单遗传算法

假设需要解决最基础的二进制优化问题:优化由 0 和 1 组成的染色体中 1 的位置。首先定义问题工厂:

Factory<Genotype<BitGene>> gtf = Genotype.of(BitChromosome.of(10, 0.5));

这里创建了长度为 10 的 BitChromosome,其中 1 的出现概率为 0.5。

接着构建执行引擎:

Engine<BitGene, Integer> engine
  = Engine.builder(SimpleGeneticAlgorithm::eval, gtf).build();

eval() 方法返回 1 的数量:

private Integer eval(Genotype<BitGene> gt) {
    return gt.getChromosome().as(BitChromosome.class).bitCount();
}

最后启动进化并收集结果:

Genotype<BitGene> result = engine.stream()
  .limit(500)
  .collect(EvolutionResult.toBestGenotype());

典型输出结果:

进化前:
[00000010|11111100]
进化后:
[00000000|11111111]

成功优化了基因中 1 的位置分布。

3.2. 子集和问题

Jenetics 可解决经典的子集和问题:给定整数集合,找出非空子集使其和为零。

Jenetics 提供了专用接口:

public class SubsetSum implements Problem<ISeq<Integer>, EnumGene<Integer>, Integer> {
    // 实现代码
}

Problem<T, G, C> 接口包含三个泛型参数:

  • - 适应度函数参数类型(此处为不可变整数序列 ISeq
  • - 进化引擎处理的基因类型(此处为可数整数基因 EnumGene
  • - 适应度函数返回类型(此处为 Integer)

需重写两个核心方法:

@Override
public Function<ISeq<Integer>, Integer> fitness() {
    return subset -> Math.abs(subset.stream()
      .mapToInt(Integer::intValue).sum());
}

@Override
public Codec<ISeq<Integer>, EnumGene<Integer>> codec() {
    return codecs.ofSubSet(basicSet, size);
}

fitness() 定义适应度函数,codec() 提供常见问题编码的工厂方法。

进入主流程,首先创建问题实例:

SubsetSum problem = of(500, 15, new LCG64ShiftRandom(101010));

注意这里使用了 Jenetics 提供的 LCG64ShiftRandom 随机数生成器。

构建解决方案引擎:

Engine<EnumGene<Integer>, Integer> engine = Engine.builder(problem)
  .minimizing()
  .maximalPhenotypeAge(5)
  .alterers(new PartiallyMatchedCrossover<>(0.4), new Mutator<>(0.3))
  .build();

通过 minimizing() 最小化结果(理想值为 0),设置表现型年龄和后代变异算子。

获取最终结果:

Phenotype<EnumGene<Integer>, Integer> result = engine.stream()
  .limit(limit.bySteadyFitness(55))
  .collect(EvolutionResult.toBestPhenotype());

使用 bySteadyFitness() 在连续 55 代无改进时终止进化。若随机集合存在解,输出类似:

[85|-76|178|-197|91|-106|-70|-243|-41|-98|94|-213|139|238|219] --> 0

否则子集和不为零。

3.3. 背包首次适配问题

Jenetics 可解决更复杂的背包问题:在有限背包空间内选择物品组合以最大化价值。

首先定义背包容量和物品数量:

int nItems = 15;
double ksSize = nItems * 100.0 / 3.0;

生成随机物品数组(包含 size 和 value 字段),使用首次适配算法随机装入背包:

KnapsackFF ff = new KnapsackFF(Stream.generate(KnapsackItem::random)
  .limit(nItems)
  .toArray(KnapsackItem[]::new), ksSize);

创建进化引擎:

Engine<BitGene, Double> engine
  = Engine.builder(ff, BitChromosome.of(nItems, 0.5))
  .populationSize(500)
  .survivorsSelector(new TournamentSelector<>(5))
  .offspringSelector(new RouletteWheelSelector<>())
  .alterers(new Mutator<>(0.115), new SinglePointCrossover<>(0.16))
  .build();

关键配置:

  • 种群规模 500
  • 后代选择采用锦标赛和轮盘赌选择
  • 定义变异算子(Mutator)和单点交叉算子

Jenetics 的强大功能:可轻松收集整个模拟过程的统计数据。 使用 EvolutionStatistics 类实现:

EvolutionStatistics<Double, ?> statistics = EvolutionStatistics.ofNumber();

执行模拟:

Phenotype<BitGene, Double> best = engine.stream()
  .limit(bySteadyFitness(7))
  .limit(100)
  .peek(statistics)
  .collect(toBestPhenotype());

每代更新统计信息,终止条件为:

  • 连续 7 代无改进
  • 或总代数达 100 代

⚠️ 必须设置最大代数限制,否则模拟可能无法在合理时间内终止。

典型结果输出:

+---------------------------------------------------------------------------+
|  时间统计                                                                 |
+---------------------------------------------------------------------------+
|             选择: 总和=0.039207931s; 平均=0.003267327s                     |
|              变异: 总和=0.065145069s; 平均=0.005428755s                     |
|   适应度计算: 总和=0.029678433s; 平均=0.002473202s                         |
|     总执行时间: 总和=0.111383965s; 平均=0.009281997s                       |
+---------------------------------------------------------------------------+
|  进化统计                                                                 |
+---------------------------------------------------------------------------+
|           进化代数: 12                                                    |
|               变异数量: 总和=7664; 平均=638.666666667                      |
|               淘汰数量: 总和=0; 平均=0.000000000                           |
|             无效个体: 总和=0; 平均=0.000000000                             |
+---------------------------------------------------------------------------+
|  种群统计                                                                 |
+---------------------------------------------------------------------------+
|                   年龄: 最大=10; 平均=1.792167; 方差=4.657748             |
|               适应度:                                                      |
|                      最小值 = 0.000000000000                              |
|                      最大值 = 716.684883338605                            |
|                      平均值 = 587.012666759785                            |
|                      方差  = 17309.892287851708                           |
|                      标准差 = 131.567063841418                            |
+---------------------------------------------------------------------------+

本次运行中,最优解价值达 716.68,同时提供了详细的进化和时间统计。

测试建议:

  1. 打开问题对应的主文件
  2. 先运行默认参数获取基准结果
  3. 调整参数(如种群规模、变异率)观察性能变化

4. 总结

本文通过真实优化问题展示了 Jenetics 库的核心特性。完整代码可在GitHub获取(Maven 项目)。

我们提供了更多优化问题的代码示例,包括:

系列文章推荐阅读:


原始标题:Introduction to Jenetics Library