遗传算法

操作过程

遗传算法由自然界的遗传机制得来,操作流程一般为: 初始化 -> 选择 -> 交叉 -> 变异 -> 迭代
下面以旅行商问题为示例:

初始化(编码 + 定义适应度函数)

  • 编码:
    将问题解用基因编码并随机生成一组候选解。
    在旅行商问题中,可以将路线转化为整数编码,随机生成n条路线作为一个种群
    eg:[0,1,2,3,4,5,6,7,8,9], [9,8,7,6,5,4,3,2,1,0], .....
  • 定义适应度函数
    定义适应度函数用来判断每个候选解的优劣,以便后续进行筛选。
    旅行商问题中需要得到总距离最短的路线,所以可以使用距离的倒数作为适应度函数
    $$
    f = \frac{1}{D}
    $$
    此处可以定义城市之间的距离矩阵进行预处理,以便后续计算

选择

按照适应度高低选择优秀个体进入下一代,常用途径有轮盘赌、锦标赛选择等。

交叉

下面举双点交叉和顺序交叉两个例子

  • 双点交叉
    随机选取两个断点i < j,交换ij中间的部分
取i = 3, j = 7
操作过程:  
P1: [123 | 4567 | 890] -> [123 6543 890] -> [1236543890]
P2: [987 | 6543 | 210] -> [987 4567 210] -> [9874567210]

显然双点交叉会使不应该重复的元素重复出现,不适用旅行商问题,所以采用顺序交叉

  • 顺序交叉
    随机选取两个断点i < j,截取P1ij中间的部分,将P2j + 1位置开始循环扫描,将未出现的城市依次填充到P1的空位
取i = 3, j = 7
P1 = [1 2 3 4 5 6 7 8 9 0] -> [_ _ 3 4 5 6 _ _ _ _] -> [2 1 3 4 5 6 0 9 8 7]
P2 = [9 8 7 6 5 4 3 2 1 0] -> [3 2 1 0 9 8 7 6 5 4] -> [2 1 0 9 8 7]
得到新序列P1,对P2同理可得

最后,将新生成的编码加入种群中。
此外,还可以使用部分匹配交叉、循环交叉等方法进行交叉。

变异

变异操作中,发生随机选取两个位置进行交换的小概率事件。
如旅行商问题中,让每个路径有1%的概率发生变异:
eg: [1 2 3 4 5 6 7 8 9 0] -> [1 9 3 4 5 6 7 8 2 0]
最后,将新生成的编码加入种群中。

迭代

可以设置最大迭代次数或目标适应度以结束迭代。
也可以设置连续n此迭代后最优路线不改变则结束迭代。
没有达到结束条件则对新得到的种群重新进行选择、交叉、变异。

代码示例:旅行商问题

下面只放算法主类,城市、种群和路径实体可以自己实现:

package ClassicProblems.TSP_genetic;  
  
import java.util.ArrayList;  
import java.util.Arrays;  
import java.util.List;  
import java.util.Random;  
  
// 遗传算法主类  
public class TSPGeneticAlgorithmWithMatrix {  
  
    private static final double mutationRate = 0.015; // 变异率  
    private static final int tournamentSize = 5;     // 锦标赛选择大小  
    private static final boolean elitism = true;     // 是否保留最优个体  
  
    // 进化种群  
    public static Population evolvePopulation(Population pop, List<City> cities, double[][] distanceMatrix) {  
        Population newPopulation = new Population(pop.populationSize());  
  
        int elitismOffset = 0;  
        // 如果启用精英主义,则保留最优个体  
        if (elitism) {  
            newPopulation.saveTour(0, new Tour(pop.getFittest().tour, distanceMatrix)); // Deep copy of best tour  
            elitismOffset = 1;  
        }  
  
        // 通过交叉产生新后代  
        for (int i = elitismOffset; i < pop.populationSize(); i++) {  
            // 选择父母  
            Tour parent1 = tournamentSelection(pop, cities, distanceMatrix);  
            Tour parent2 = tournamentSelection(pop, cities, distanceMatrix);  
            // 交叉产生孩子  
            Tour child = crossover(parent1, parent2, distanceMatrix);  
            newPopulation.saveTour(i, child);  
        }  
  
        // 对新种群进行变异(跳过精英个体)  
        for (int i = elitismOffset; i < newPopulation.populationSize(); i++) {  
            mutate(newPopulation.getTour(i));  
        }  
  
        return newPopulation;  
    }  
  
    // 交叉操作 (顺序交叉 OX1) - 修复版  
    public static Tour crossover(Tour parent1, Tour parent2, double[][] distanceMatrix) {  
        Random rand = new Random();  
  
        // 处理边界情况:如果只有一个或没有城市,直接返回父代副本  
        if (parent1.tourSize() < 2) {  
            return new Tour(parent1.tour, distanceMatrix);  
        }  
  
        // 随机选择交叉点 (确保 start <= end,并且至少留一个位置用于交叉)  
        int start = rand.nextInt(parent1.tourSize());  
        // end 至少是 start + 1,最多是 tourSize - 1        int end = start + rand.nextInt(parent1.tourSize() - start);  
        // 如果需要 start < end 的严格小于关系,可以调整上面一行  
        // 例如: int end = start + 1 + rand.nextInt(parent1.tourSize() - start - 1);  
        // 但允许 start==end 也是可以的,代表只复制一个基因  
  
        // 为了确保 start <= end (如果上面的逻辑没保证)  
        if (start > end) {  
            int temp = start;  
            start = end;  
            end = temp;  
        }  
  
        // 1. 创建一个空的子代 Tour        Tour child = new Tour(distanceMatrix);  
  
        // 2. *** 关键修复:预先填充 child 的 tour 列表,使其具有正确的大小 ***        //    这里我们用 null 占位,稍后会被实际城市或保留片段填充  
        for (int i = 0; i < parent1.tourSize(); i++) {  
            child.tour.add(null); // 或者 child.setCity(i, null) 如果你更喜欢用 setCity        }  
  
        // 3. 将 parent1 的 [start, end] 片段直接复制到 child 的相同位置  
        for (int i = start; i <= end; i++) {  
            child.setCity(i, parent1.getCity(i));  
        }  
  
        // 4. 从 parent2 获取城市顺序,并填充 child 中剩余的空位  
        int childIndex = 0;  
        for (int i = 0; i < parent2.tourSize(); i++) {  
            City cityFromParent2 = parent2.getCity(i);  
  
            // 如果这个城市不在 child 已经保留的片段中  
            if (!child.containsCity(cityFromParent2)) {  
  
                // 找到 child 中下一个可用的 (为 null 的) 位置  
                while (childIndex < child.tourSize() && child.getCity(childIndex) != null) {  
                    childIndex++;  
                }  
  
                // 如果找到了位置,则放置城市  
                if (childIndex < child.tourSize()) {  
                    child.setCity(childIndex, cityFromParent2);  
                    // childIndex++; // 这行可以加也可以不加,下次循环开始时会继续找下一个null位  
                }  
            }  
        }  
  
        // 5. 返回构建好的子代  
        return child;  
    }  
  
    // 变异操作 (交换变异)  
    private static void mutate(Tour tour) {  
        Random rand = new Random();  
        for (int i = 0; i < tour.tourSize(); i++) {  
            if (rand.nextDouble() < mutationRate) {  
                int j = rand.nextInt(tour.tourSize());  
  
                City city1 = tour.getCity(i);  
                City city2 = tour.getCity(j);  
  
                tour.setCity(j, city1);  
                tour.setCity(i, city2);  
            }  
        }  
    }  
  
    // 锦标赛选择  
    private static Tour tournamentSelection(Population pop, List<City> cities, double[][] distanceMatrix) {  
        Population tournament = new Population(tournamentSize);  
  
        for (int i = 0; i < tournamentSize; i++) {  
            int randomId = (int) (Math.random() * pop.populationSize());  
            Tour selectedTour = pop.getTour(randomId);  
            // Ensure we don't add null tours to the tournament  
            if (selectedTour != null) {  
                tournament.saveTour(i, new Tour(selectedTour.tour, distanceMatrix)); // Copy  
            } else {  
                // If selected tour is null, create a random one for the tournament  
                Tour randomTour = new Tour(distanceMatrix).generateIndividual(cities);  
                tournament.saveTour(i, randomTour);  
            }  
        }  
        Tour fittest = tournament.getFittest();  
        return fittest;  
    }  
  
  
    public static void main(String[] args) {  
  
        // 定义城市 (这里仍然用ID来标识)  
        List<City> cities = new ArrayList<>();  
        int numCities = 5;  
        for (int i = 0; i < numCities; i++) {  
            cities.add(new City(i, "C" + i));  
        }  
  
        // 定义距离矩阵 (非对称示例)  
        // C0  C1  C2  C3  C4        double[][] distanceMatrix = {  
                {0, 10, 15, 20, 25}, // From C0  
                {12, 0, 8, 17, 22},  // From C1  
                {20, 25, 0, 9, 14},  // From C2  
                {10, 15, 20, 0, 11}, // From C3  
                {30, 35, 5, 15, 0}   // From C4  
        };  
  
        System.out.println("城市列表: " + cities);  
        System.out.println("城市数量: " + cities.size());  
        System.out.println("距离矩阵:");  
        for (int i = 0; i < distanceMatrix.length; i++) {  
            System.out.println(Arrays.toString(distanceMatrix[i]));  
        }  
  
        // 初始化种群  
        int populationSize = 20;  
        Population population = new Population(populationSize, 0); // Use dummy constructor  
        // 手动填充初始种群  
        for (int i = 0; i < populationSize; i++) {  
            Tour newTour = new Tour(distanceMatrix).generateIndividual(cities);  
            population.saveTour(i, newTour);  
        }  
  
        System.out.println("\n初始种群最优距离: " + population.getFittest().getDistance());  
  
        // 进化种群  
        int numberOfGenerations = 500;  
        for (int i = 0; i < numberOfGenerations; i++) {  
            population = evolvePopulation(population, cities, distanceMatrix);  
            if (i % 100 == 0) { // 每100代打印一次信息  
                Tour currentBest = population.getFittest();  
                if (currentBest != null) {  
                    System.out.println("第 " + i + " 代 最优距离: " + currentBest.getDistance());  
                }  
            }  
        }  
  
        System.out.println("\n--- 进化完成 ---");  
        Tour finalBest = population.getFittest();  
        if (finalBest != null) {  
            System.out.println("最终最优距离: " + finalBest.getDistance());  
            System.out.println("最终最优路径: ");  
            System.out.println(finalBest);  
        } else {  
            System.out.println("未能找到有效路径。");  
        }  
    }  
}
posted @ 2025-11-21 23:48  Capache  阅读(17)  评论(0)    收藏  举报