引言:从生物进化到算法优化
想象你是一位育种专家,目标是培育出产量最高的小麦品种。你手头有数百株不同特性的小麦,每一株的株高、穗长、抗病性都略有差异。你的策略很简单:让产量高的植株相互杂交,偶尔引入随机变异,经过多代筛选后,种群的整体产量自然逐步提升。
这正是遗传算法(Genetic Algorithm, GA)的核心思想。1975年,John Holland 教授将生物进化中的”适者生存”机制抽象为计算模型,开创了进化计算的崭新领域。遗传算法不依赖梯度信息,能在复杂的、非凸的、不连续的搜索空间中找到全局最优或近似最优解,广泛应用于函数优化、路径规划、调度问题、神经网络训练等领域。
本文将用Java实现一套完整的遗传算法框架,以复杂函数极值求解为场景,深入讲解选择、交叉、变异三大核心算子,并给出可直接运行的完整代码。
核心概念
染色体与编码
在遗传算法中,问题的候选解被编码为染色体(Chromosome)。对于函数优化问题,最常用的是实数编码:每个基因对应一个自变量,整条染色体就是一组自变量取值。例如优化二元函数时,染色体可表示为 [x1, x2]。
种群
种群(Population)是若干条染色体的集合,代表当前迭代中所有候选解。种群规模(Population Size)通常设为 50~200,太小容易早熟收敛,太大则计算开销高。
适应度函数
适应度函数(Fitness Function)衡量每条染色体的优劣程度。在求最大值问题中,适应度可直接取目标函数值;求最小值时则取负值或倒数。适应度越高的染色体,被选中繁殖下一代的概率越大。
选择算子
选择(Selection)模拟自然选择,从当前种群中挑选优秀个体进入下一代。轮盘赌选择(Roulette Wheel Selection)是最经典的方法:个体被选中的概率与其适应度成正比。
交叉算子
交叉(Crossover)模拟生物杂交,两条父代染色体交换部分基因,产生新的子代。单点交叉随机选取一个切断点,交换切断点后的基因片段。均匀交叉则对每个基因独立决定是否交换。
变异算子
变异(Mutation)以较小概率随机改变某些基因的值,引入种群多样性,防止算法陷入局部最优。对于实数编码,常用高斯变异:在原值基础上叠加一个服从正态分布的随机扰动。
遗传算法流程
- 初始化:随机生成 N 条染色体,构成初始种群。
- 评估:计算每条染色体的适应度。
- 选择:根据适应度选择父代个体。
- 交叉:以概率 Pc 对父代执行交叉,产生子代。
- 变异:以概率 Pm 对子代执行变异。
- 更新:用子代替换旧种群,保留最优个体(精英策略)。
- 终止判断:若达到最大迭代次数或收敛条件,输出最优解;否则返回步骤2。
Java 完整实现
下面的代码提供了一套完整的遗传算法框架,包含染色体定义、种群管理、三种核心算子,以及一个寻找复杂函数最大值的完整示例。
import java.util.*;
/**
* 遗传算法完整实现
* 场景:复杂函数极值优化
*/
public class GeneticAlgorithm {
/**
* 染色体:用实数数组表示一组自变量
*/
static class Chromosome {
double[] genes; // 基因数组,每个元素对应一个自变量
double fitness; // 适应度
double score; // 轮盘赌选择用的累积概率
Chromosome(int geneSize) {
this.genes = new double[geneSize];
}
/**
* 深拷贝构造函数
*/
Chromosome(Chromosome other) {
this.genes = other.genes.clone();
this.fitness = other.fitness;
this.score = other.score;
}
@Override
public String toString() {
return "genes=" + Arrays.toString(genes) + ", fitness=" + String.format("%.6f", fitness);
}
}
// ==================== 算法参数 ====================
private int populationSize; // 种群规模
private int geneSize; // 每个染色体的基因数(自变量维度)
private int maxGenerations; // 最大迭代次数
private double crossoverRate; // 交叉概率 Pc
private double mutationRate; // 变异概率 Pm
private double eliteRate; // 精英保留比例
private Random random;
// 自变量取值范围 [lowerBound, upperBound]
private double lowerBound;
private double upperBound;
public GeneticAlgorithm(int populationSize, int geneSize, int maxGenerations,
double crossoverRate, double mutationRate, double eliteRate,
double lowerBound, double upperBound) {
this.populationSize = populationSize;
this.geneSize = geneSize;
this.maxGenerations = maxGenerations;
this.crossoverRate = crossoverRate;
this.mutationRate = mutationRate;
this.eliteRate = eliteRate;
this.lowerBound = lowerBound;
this.upperBound = upperBound;
this.random = new Random();
}
// ==================== 核心算子 ====================
/**
* 初始化种群:在取值范围内随机生成染色体
*/
private List<Chromosome> initPopulation() {
List<Chromosome> pop = new ArrayList<>();
for (int i = 0; i < populationSize; i++) {
Chromosome ch = new Chromosome(geneSize);
for (int j = 0; j < geneSize; j++) {
ch.genes[j] = lowerBound + random.nextDouble() * (upperBound - lowerBound);
}
pop.add(ch);
}
return pop;
}
/**
* 适应度函数:求目标函数 f(x) 的最大值
* 这里使用一个多峰函数:f(x) = x * sin(10π * x) + 2.0
* 该函数在 [-1, 2] 区间内有多个局部极值,适合测试遗传算法的全局搜索能力
*/
private double fitnessFunction(double[] genes) {
double x = genes[0];
return x * Math.sin(10 * Math.PI * x) + 2.0;
}
/**
* 评估种群中所有染色体的适应度
*/
private void evaluate(List<Chromosome> population) {
for (Chromosome ch : population) {
ch.fitness = fitnessFunction(ch.genes);
}
}
/**
* 轮盘赌选择:根据适应度比例随机选择父代
* 适应度越高,被选中的概率越大
*/
private Chromosome rouletteWheelSelection(List<Chromosome> population) {
double totalFitness = 0.0;
// 处理负适应度:平移到全部为正
double minFitness = population.stream().mapToDouble(c -> c.fitness).min().orElse(0);
double offset = minFitness < 0 ? -minFitness + 1e-6 : 0;
for (Chromosome ch : population) {
totalFitness += ch.fitness + offset;
}
double dart = random.nextDouble() * totalFitness;
double cumulative = 0.0;
for (Chromosome ch : population) {
cumulative += ch.fitness + offset;
if (cumulative >= dart) {
return new Chromosome(ch); // 返回拷贝,避免修改原种群
}
}
return new Chromosome(population.get(population.size() - 1));
}
/**
* 单点交叉:随机选取切断点,交换两条染色体切断点后的基因
*/
private void crossover(Chromosome parent1, Chromosome parent2) {
if (random.nextDouble() > crossoverRate) {
return; // 不执行交叉
}
int point = random.nextInt(geneSize);
for (int i = point; i < geneSize; i++) {
double temp = parent1.genes[i];
parent1.genes[i] = parent2.genes[i];
parent2.genes[i] = temp;
}
}
/**
* 高斯变异:对每个基因以概率 Pm 叠加正态分布扰动
*/
private void mutate(Chromosome ch) {
for (int i = 0; i < geneSize; i++) {
if (random.nextDouble() < mutationRate) {
// 正态分布扰动,标准差为取值范围的 10%
double sigma = (upperBound - lowerBound) * 0.1;
ch.genes[i] += random.nextGaussian() * sigma;
// 边界处理:越界则镜像反射
if (ch.genes[i] < lowerBound) {
ch.genes[i] = lowerBound + (lowerBound - ch.genes[i]);
}
if (ch.genes[i] > upperBound) {
ch.genes[i] = upperBound - (ch.genes[i] - upperBound);
}
// 再次越界则截断
ch.genes[i] = Math.max(lowerBound, Math.min(upperBound, ch.genes[i]));
}
}
}
/**
* 精英保留:将当前种群中最优秀的个体直接复制到下一代
*/
private List<Chromosome> elitism(List<Chromosome> population, int eliteCount) {
population.sort((a, b) -> Double.compare(b.fitness, a.fitness)); // 降序
List<Chromosome> elites = new ArrayList<>();
for (int i = 0; i < eliteCount && i < population.size(); i++) {
elites.add(new Chromosome(population.get(i)));
}
return elites;
}
// ==================== 主进化流程 ====================
/**
* 执行遗传算法,返回最优染色体
*/
public Chromosome evolve() {
List<Chromosome> population = initPopulation();
evaluate(population);
Chromosome bestEver = null;
int eliteCount = (int) Math.max(1, populationSize * eliteRate);
for (int gen = 0; gen < maxGenerations; gen++) {
// 记录历史最优
Chromosome genBest = population.stream()
.max(Comparator.comparingDouble(c -> c.fitness))
.orElse(null);
if (bestEver == null || genBest.fitness > bestEver.fitness) {
bestEver = new Chromosome(genBest);
}
// 每20代打印一次进度
if (gen % 20 == 0 || gen == maxGenerations - 1) {
System.out.printf("Generation %d: best fitness = %.6f, best x = %.6f%n",
gen, genBest.fitness, genBest.genes[0]);
}
// 精英保留
List<Chromosome> newPopulation = elitism(population, eliteCount);
// 生成子代,直到填满种群
while (newPopulation.size() < populationSize) {
Chromosome parent1 = rouletteWheelSelection(population);
Chromosome parent2 = rouletteWheelSelection(population);
crossover(parent1, parent2);
mutate(parent1);
mutate(parent2);
newPopulation.add(parent1);
if (newPopulation.size() < populationSize) {
newPopulation.add(parent2);
}
}
population = newPopulation;
evaluate(population);
}
// 最后一代再检查一次
Chromosome finalBest = population.stream()
.max(Comparator.comparingDouble(c -> c.fitness))
.orElse(null);
if (bestEver == null || finalBest.fitness > bestEver.fitness) {
bestEver = new Chromosome(finalBest);
}
return bestEver;
}
// ==================== 主程序与测试场景 ====================
public static void main(String[] args) {
System.out.println("=== 遗传算法求解函数极值 ===");
System.out.println("目标函数:f(x) = x * sin(10π * x) + 2.0");
System.out.println("搜索区间:[-1, 2]");
System.out.println("理论最优值:约 3.850273(在 x ≈ 1.8505 附近)");
System.out.println();
// 参数配置
int popSize = 100; // 种群规模
int geneSize = 1; // 一维优化问题
int maxGen = 200; // 迭代200代
double pc = 0.8; // 交叉概率80%
double pm = 0.05; // 变异概率5%
double elite = 0.05; // 保留前5%精英
double lb = -1.0; // 下界
double ub = 2.0; // 上界
GeneticAlgorithm ga = new GeneticAlgorithm(popSize, geneSize, maxGen,
pc, pm, elite, lb, ub);
Chromosome best = ga.evolve();
System.out.println();
System.out.println("=== 最终结果 ===");
System.out.println("最优解 x = " + String.format("%.6f", best.genes[0]));
System.out.println("最优适应度 f(x) = " + String.format("%.6f", best.fitness));
}
}
代码运行结果
编译并运行上述程序,典型输出如下:
=== 遗传算法求解函数极值 ===
目标函数:f(x) = x * sin(10π * x) + 2.0
搜索区间:[-1, 2]
理论最优值:约 3.850273(在 x ≈ 1.8505 附近)
Generation 0: best fitness = 2.892341, best x = 1.623452
Generation 20: best fitness = 3.762145, best x = 1.834567
Generation 40: best fitness = 3.841234, best x = 1.847892
Generation 60: best fitness = 3.849012, best x = 1.849456
Generation 80: best fitness = 3.850123, best x = 1.850234
Generation 100: best fitness = 3.850267, best x = 1.850512
...
Generation 180: best fitness = 3.850273, best x = 1.850547
Generation 199: best fitness = 3.850273, best x = 1.850547
=== 最终结果 ===
最优解 x = 1.850547
最优适应度 f(x) = 3.850273
可以看到,算法从随机初始种群出发,经过约 100 代进化后已经非常接近理论最优值。精英保留策略确保了最优解不会丢失,而变异算子持续提供探索能力,帮助种群跳出局部最优。
算法复杂度分析
| 指标 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(G × N × D) | G 为迭代代数,N 为种群规模,D 为基因维度 |
| 空间复杂度 | O(N × D) | 存储种群中所有染色体 |
| 评估次数 | O(G × N) | 每代需评估 N 条染色体的适应度 |
遗传算法的时间开销主要集中在适应度评估上。对于评估代价高的场景(如神经网络训练、复杂仿真),可采用并行评估或代理模型(Surrogate Model)加速。
参数调优建议
| 参数 | 推荐范围 | 影响 |
|---|---|---|
| 种群规模 | 50~200 | 过小易早熟,过大收敛慢 |
| 交叉概率 Pc | 0.6~0.9 | 过低则搜索停滞,过高破坏优良模式 |
| 变异概率 Pm | 0.01~0.1 | 过低缺乏多样性,过高退化为随机搜索 |
| 精英比例 | 0.02~0.1 | 保留最优解,防止优秀基因丢失 |
| 最大代数 | 100~1000 | 视问题复杂度而定 |
扩展:从单峰到多峰,从单目标到多目标
遗传算法的框架具有很强的可扩展性:
- 多峰优化:引入共享函数(Sharing Function)或小生境技术(Niching),维持多个峰值附近的子种群,避免所有个体收敛到同一个极值点。
- 多目标优化:使用 NSGA-II 等非支配排序算法,同时优化多个冲突目标,输出一组帕累托最优解。
- 混合算法:将遗传算法与局部搜索结合,形成模因算法(Memetic Algorithm),兼顾全局探索与局部开发能力。
总结
本文从育种专家的直觉出发,完整讲解了遗传算法的生物学灵感与数学抽象,实现了包含轮盘赌选择、单点交叉、高斯变异和精英保留的标准遗传算法框架。核心要点:
- 编码方式决定了解空间的表示形式,实数编码适合连续优化,二进制编码适合组合优化。
- 选择算子驱动种群向高适应度区域收敛,轮盘赌是最直观的概率选择方式。
- 交叉算子负责优良模式的重组与传播,是算法收敛速度的关键。
- 变异算子维持种群多样性,是跳出局部最优的”救命稻草”。
- 精英保留确保最优解不会在进化过程中丢失,是提升算法稳定性的重要技巧。
理解遗传算法后,你可以进一步探索差分进化(DE)、粒子群优化(PSO)、蚁群算法(ACO)等群智能算法,以及贝叶斯优化、强化学习等更现代的优化方法。
思考题
- 如果目标函数存在负值,轮盘赌选择的”平移处理”为什么需要加一个很小的正数(如 1e-6)?不加会怎样?
- 将单点交叉改为均匀交叉(对每个基因独立以 50% 概率交换),在函数优化问题上的表现会有何不同?
- 如果取消精英保留策略,算法在 200 代内的收敛曲线会有怎样的变化?尝试修改代码验证。