每日算法 — 使用java实现瘟疫公司:差分进化与病毒参数优化

引言

瘟疫公司(Plague Inc.)是一款广受欢迎的策略模拟游戏,玩家扮演病原体,通过进化传播能力、症状和耐药性,最终感染并消灭全人类。要在游戏中取得高分,关键在于如何优化病原体的各项参数(传染性、致命性、耐药性等),使其在不同气候、人口密度和医疗水平的国家中实现最优传播。本文将用Java实现基于差分进化(Differential Evolution, DE)算法的病毒参数自动优化系统,讲解DE算法的变异、交叉与选择三大核心操作,构建一个能自动搜索最优进化策略的智能引擎。

核心算法设计

1. 参数编码与适应度函数

将病原体的进化策略编码为实数向量X = [x₁, x₂, …, xₙ],每个维度代表一项属性:
– x₁:空气传播等级(0~5)
– x₂:水源传播等级(0~5)
– x₃:动物传播等级(0~5)
– x₄:症状严重性(0~10)
– x₅:致命性(0~10)
– x₆:抗寒性(0~5)
– x₇:抗热性(0~5)
– x₈:耐药性(0~5)

适应度函数综合考虑感染速度、死亡率和被治愈风险:
Fitness(X) = w₁×InfectRate(X) + w₂×Lethality(X) – w₃×CureRisk(X)

2. 差分进化算法

DE是一种基于群体智能的进化优化算法,通过向量差分实现搜索方向的自适应调整:

变异:对于每个目标向量Xᵢ,随机选择三个不同个体Xᵣ₁, Xᵣ₂, Xᵣ₃,生成变异向量:
Vᵢ = Xᵣ₁ + F × (Xᵣ₂ – Xᵣ₃)
其中F∈[0,2]为缩放因子,控制差分扰动幅度。

交叉:将变异向量Vᵢ与目标向量Xᵢ按概率CR进行二项交叉,生成试验向量Uᵢ:
Uᵢⱼ = Vᵢⱼ if rand() < CR or j == j_rand
Uᵢⱼ = Xᵢⱼ otherwise

选择:比较试验向量与目标向量的适应度,保留更优者:
Xᵢ_new = Uᵢ if f(Uᵢ) ≤ f(Xᵢ) else Xᵢ
(最小化问题,若最大化则取反)

3. 约束处理

病原体进化受DNA点数约束:Σcost(xᵢ) ≤ DNA_Points。采用罚函数法,将违反约束的解适应度大幅惩罚。

Java实现

import java.util.*;

/**
 * 瘟疫公司病毒参数优化系统
 * 核心算法:差分进化(Differential Evolution)
 */
public class PlagueIncOptimizer {

    // ==================== 病毒参数模型 ====================

    static class VirusParams {
        double[] genes; // [空气,水源,动物,症状,致命性,抗寒,抗热,耐药]
        static final String[] GENE_NAMES = {
            "空气传播", "水源传播", "动物传播", "症状严重性",
            "致命性", "抗寒性", "抗热性", "耐药性"
        };
        static final double[] MIN_VAL = {0, 0, 0, 0, 0, 0, 0, 0};
        static final double[] MAX_VAL = {5, 5, 5, 10, 10, 5, 5, 5};
        static final double[] COST = {1, 1, 1, 2, 3, 1, 1, 2}; // 每级DNA消耗

        VirusParams() {
            this.genes = new double[8];
        }

        VirusParams(double[] genes) {
            this.genes = genes.clone();
        }

        /** 将基因值裁剪到合法范围 */
        void clip() {
            for (int i = 0; i < genes.length; i++) {
                genes[i] = Math.max(MIN_VAL[i], Math.min(MAX_VAL[i], genes[i]));
            }
        }

        /** 计算总DNA消耗 */
        double totalCost() {
            double sum = 0;
            for (int i = 0; i < genes.length; i++) {
                sum += genes[i] * COST[i];
            }
            return sum;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder("[");
            for (int i = 0; i < genes.length; i++) {
                sb.append(String.format("%s=%.1f", GENE_NAMES[i], genes[i]));
                if (i < genes.length - 1) sb.append(", ");
            }
            sb.append("]");
            return sb.toString();
        }
    }

    // ==================== 传播模拟与适应度评估 ====================

    static class WorldModel {
        int countries = 20;          // 国家数量
        double avgPopDensity = 100;  // 平均人口密度
        double avgWealth = 50;       // 平均富裕程度(影响医疗)
        double climateVar = 0.3;     // 气候变异系数

        /**
         * 模拟传播并计算适应度
         * @param vp 病毒参数
         * @return 适应度值(越高越好)
         */
        double evaluate(VirusParams vp) {
            double air = vp.genes[0];
            double water = vp.genes[1];
            double animal = vp.genes[2];
            double symptom = vp.genes[3];
            double lethality = vp.genes[4];
            double coldRes = vp.genes[5];
            double heatRes = vp.genes[6];
            double drugRes = vp.genes[7];

            // DNA点数约束检查
            double dnaCost = vp.totalCost();
            double maxDNA = 60; // 可用DNA点数上限
            if (dnaCost > maxDNA) {
                // 罚函数:超出越多惩罚越重
                return -100 * (dnaCost - maxDNA);
            }

            // 传播率计算:基础传播 + 各途径加成
            double baseSpread = 0.05;
            double spreadRate = baseSpread
                + air * 0.08
                + water * 0.06
                + animal * 0.04
                + (coldRes + heatRes) * 0.02; // 气候适应性提升全球传播

            // 感染速度模拟(简化SIR模型中的感染项)
            double infectSpeed = spreadRate * avgPopDensity / 10.0;
            // 症状和致命性加快被发现和研发解药
            double detectability = symptom * 0.05 + lethality * 0.08;
            double cureSpeed = (avgWealth / 100.0) * (1 + detectability) * (1 - drugRes * 0.15);

            // 综合评分:感染速度 + 致命性×持续时间 - 被治愈风险
            double duration = Math.max(1.0, 50.0 / (cureSpeed + 0.1)); // 解药完成前持续时间
            double totalInfected = infectSpeed * duration * countries;
            double totalDeaths = totalInfected * (lethality / 10.0) * 0.3;

            // 适应度:加权综合
            double fitness = totalInfected * 0.4
                + totalDeaths * 0.5
                - detectability * 20   // 过早暴露的惩罚
                - cureSpeed * 10;       // 解药速度惩罚

            return fitness;
        }
    }

    // ==================== 差分进化算法核心 ====================

    static class DifferentialEvolution {
        int popSize;           // 种群大小
        int dim;               // 维度数
        double F;              // 缩放因子
        double CR;             // 交叉概率
        int maxGen;            // 最大迭代次数
        Random rand = new Random(42);
        WorldModel world;

        DifferentialEvolution(int popSize, int dim, double F, double CR, int maxGen, WorldModel world) {
            this.popSize = popSize;
            this.dim = dim;
            this.F = F;
            this.CR = CR;
            this.maxGen = maxGen;
            this.world = world;
        }

        /**
         * 初始化种群:在边界内随机生成
         */
        List<VirusParams> initPopulation() {
            List<VirusParams> pop = new ArrayList<>();
            for (int i = 0; i < popSize; i++) {
                VirusParams vp = new VirusParams();
                for (int j = 0; j < dim; j++) {
                    vp.genes[j] = VirusParams.MIN_VAL[j]
                        + rand.nextDouble() * (VirusParams.MAX_VAL[j] - VirusParams.MIN_VAL[j]);
                }
                pop.add(vp);
            }
            return pop;
        }

        /**
         * 执行DE优化
         */
        VirusParams optimize() {
            List<VirusParams> pop = initPopulation();
            double[] fitness = new double[popSize];
            for (int i = 0; i < popSize; i++) {
                fitness[i] = world.evaluate(pop.get(i));
            }

            VirusParams best = null;
            double bestFit = Double.NEGATIVE_INFINITY;

            for (int g = 0; g < maxGen; g++) {
                for (int i = 0; i < popSize; i++) {
                    // 1. 变异:随机选择3个不同个体
                    int r1, r2, r3;
                    do { r1 = rand.nextInt(popSize); } while (r1 == i);
                    do { r2 = rand.nextInt(popSize); } while (r2 == i || r2 == r1);
                    do { r3 = rand.nextInt(popSize); } while (r3 == i || r3 == r1 || r3 == r2);

                    VirusParams xr1 = pop.get(r1);
                    VirusParams xr2 = pop.get(r2);
                    VirusParams xr3 = pop.get(r3);

                    // V = Xr1 + F * (Xr2 - Xr3)
                    VirusParams mutant = new VirusParams();
                    for (int j = 0; j < dim; j++) {
                        mutant.genes[j] = xr1.genes[j] + F * (xr2.genes[j] - xr3.genes[j]);
                    }
                    mutant.clip();

                    // 2. 交叉:二项交叉生成试验向量
                    VirusParams trial = new VirusParams(pop.get(i).genes.clone());
                    int jRand = rand.nextInt(dim);
                    for (int j = 0; j < dim; j++) {
                        if (rand.nextDouble() < CR || j == jRand) {
                            trial.genes[j] = mutant.genes[j];
                        }
                    }
                    trial.clip();

                    // 3. 选择:优胜劣汰
                    double trialFit = world.evaluate(trial);
                    if (trialFit > fitness[i]) {
                        pop.set(i, trial);
                        fitness[i] = trialFit;
                    }

                    // 更新全局最优
                    if (fitness[i] > bestFit) {
                        bestFit = fitness[i];
                        best = new VirusParams(pop.get(i).genes);
                    }
                }

                if ((g + 1) % 50 == 0) {
                    System.out.println("  第" + (g + 1) + "代, 最优适应度=" + String.format("%.2f", bestFit));
                }
            }
            return best;
        }
    }

    // ==================== 主程序与测试 ====================

    public static void main(String[] args) {
        System.out.println("===== 瘟疫公司病毒参数优化系统 =====\n");
        System.out.println("核心算法:差分进化(Differential Evolution)");
        System.out.println("优化目标:最大化全球感染与致命效果,同时规避解药研发\n");

        WorldModel world = new WorldModel();

        // DE参数配置
        int popSize = 50;
        int dim = 8;
        double F = 0.8;    // 缩放因子
        double CR = 0.9;   // 交叉概率
        int maxGen = 300;  // 迭代次数

        DifferentialEvolution de = new DifferentialEvolution(popSize, dim, F, CR, maxGen, world);

        long start = System.currentTimeMillis();
        VirusParams best = de.optimize();
        long elapsed = System.currentTimeMillis() - start;

        System.out.println("\n===== 优化结果 =====");
        System.out.println("最优病毒参数: " + best);
        System.out.println("DNA总消耗: " + String.format("%.1f", best.totalCost()) + " / 60");
        System.out.println("最优适应度: " + String.format("%.2f", world.evaluate(best)));
        System.out.println("优化耗时: " + elapsed + "ms");

        System.out.println("\n参数解读:");
        for (int i = 0; i < best.genes.length; i++) {
            int level = (int) Math.round(best.genes[i]);
            String bar = "█".repeat(level) + "░".repeat((int)(VirusParams.MAX_VAL[i]) - level);
            System.out.println("  " + VirusParams.GENE_NAMES[i] + ": [" + bar + "] " + level);
        }
    }
}

复杂度分析

操作 时间复杂度 空间复杂度 说明
种群初始化 O(N×D) O(N×D) N为种群大小,D为维度
变异操作 O(N×D) O(D) 每代每个体生成一个变异向量
交叉操作 O(N×D) O(D) 二项交叉
适应度评估 O(N×D) O(1) 每代评估N个试验向量
选择操作 O(N) O(1) 逐个比较
整体优化 O(G×N×D) O(N×D) G为迭代代数

算法扩展思路

  1. 自适应参数调整:采用jDE或SaDE策略,让F和CR在进化过程中自适应调整,提升收敛速度和全局搜索能力。
  2. 多目标优化:将感染速度、致命性、隐蔽性作为独立的帕累托目标,使用NSGA-II等算法求解非支配解集。
  3. 元启发式混合:将DE的变异机制与模拟退火的Metropolis准则结合,增强跳出局部最优的能力。

总结

本文将瘟疫公司的病原体进化策略抽象为实数向量优化问题,通过差分进化算法自动搜索最优参数组合。DE算法利用向量差分实现自适应搜索方向的生成,在二项交叉和贪婪选择中平衡探索与利用,是求解连续空间组合优化问题的有力工具。该框架不仅适用于游戏AI,也可扩展至药物配比优化、投资组合配置等现实决策场景。