遗传算法(GA)解决旅行家问题(TSP) - Implemented By Java

问题引入

旅行家问题(Travelling salesman problem,TSP)是这样一个问题:给定一系列城市以及每两个城市之间的距离,要求从某一点出发经过其余每一个点之后任回到该点,在保证每个点只能经过一次的情况下,求产生最短路径的城市序列。从图论的角度来看,该问题实质是在一个带权完全无向图中,找一个权值最小的哈密尔顿(Hamilton)回路。该问题属于NP完全问题,随着城市数量的增加,任何TSP算法最坏情况下的运行时间都有可能展示出超高(指数)级别的增长。早期的研究者使用精确算法求解该问题,常用的方法包括:分枝定界法、线性规划法、动态规划法等。但是,随着问题规模的增大,精确算法将变得无能为力,因此,在后来的研究中,国内外学者重点使用近似算法或启发式算法,主要有遗传算法、模拟退火法、蚁群算法、禁忌搜索算法、贪婪算法和神经网络等。


本文概述

本文采用遗传算法(Genetic Algorithm)解决旅行家问题。本文首先通过一个例子讲解了什么是遗传算法,然后依次给出了相关的数据结构和关键功能的代码实现。文章末给出了全部代码和数据集。
本文并没有做数据可视化,有兴趣的小伙伴可以动手试一试。

不足之处敬请指正


遗传算法

1. 什么是遗传算法?

遗传算法模拟物竞天择的生物进化过程,通过维护一个潜在解的群体执行了多方向的搜索,并支持这些方向上的信息构成和交换。是以面为单位的搜索,比以点为单位的搜索,更能发现全局最优解。
在遗传算法中,有很多袋鼠,它们降落到喜玛拉雅山脉的任意地方。这些袋鼠并不知道它们的任务是寻找珠穆朗玛峰。但每过几年,就在一些海拔高度较低的地方射杀一些袋鼠,并希望存活下来的袋鼠是多产的,在它们所处的地方生儿育女。或者换个说法。从前,有一大群袋鼠,它们被莫名其妙的零散地遗弃于喜马拉雅山脉。于是只好在那里艰苦的生活。海拔低的地方弥漫着一种无色无味的毒气,海拔越高毒气越稀薄。可是可怜的袋鼠们对此全然不觉,还是习惯于活蹦乱跳。于是,不断有袋鼠死于海拔较低的地方,而越是在海拔高的袋鼠越是能活得更久,也越有机会生儿育女。就这样经过许多年,这些袋鼠们竟然都不自觉地聚拢到了一个个的山峰上,可是在所有的袋鼠中,只有聚拢到珠穆朗玛峰的袋鼠被带回了美丽的澳洲。

说到底,遗传算法就是不断通过操作父代得到子代,再通过父代子代更新父代的一个循环往复从而得到更优解的一个过程。

2. 遗传算法的大体流程

下文会用到的相关“术语”(个人喜好):

  • 种群:相同类型的个体的集合叫做种群
  • 群体:相同代个体的集合叫做群体,例如父代群体和子代群体
  • 个体: 参与遗传的最小单位;表现性的承载者
  • 表现型:个体所表示在外的特征或是状态
  • 基因型:个体基因的具体序列;基因型决定表现性,也就是多个基因型可能会导致同一种表现型
  • 繁殖: 一次繁殖可包括交叉和变异
  • 选择: 对父代群体进行选择个体操作
  • 交叉: 选取父代群体中的两个个体(父亲和母亲)进行交叉遗传交换基因操作
  • 变异: 对子代进行基因突变的操作
  • 择优:选取优秀个体,使其进入下一次繁殖
  • 适应度:个体适应当前环境的程度

步骤如下:

  1. 初始化种群,生成父代群体
  2. 对父代进行选择操作(轮盘赌策略)
  3. 用父代个体进行繁殖操作(交叉和变异)
  4. 进行择优操作:综合父代群体和子代群体选取优秀个体
  5. 若最优个体不满足要求(或未达到最大迭代次数),返回至第2步;否则停止循环,输出。

详解:

  1. 在TSP问题中,每个个体的染色体其实就是一个特定的城市序列。
  2. 为了保证表现型越好(路径越短)的个体适应度越高,我们用路径长度的倒数作为个体的适应度。
  3. 染色体中的每个基因就是一个城市,每个城市携带着其编号与坐标信息。
  4. 轮盘赌策略是一种累加选择的方式,依次累加个体的概率直到概率和达到期望值,则选取最后一个累加的个体(下文讲解)。
  5. 在TSP问题中,交叉操作交换基因时会引起冲突(一个基因在染色体中出现多次),这时要解决冲突(下文讲解)。

各参数设定推荐(个人经验):

变异概率交叉概率种群大小繁殖次数
0.01 - 0.10.8 - 1.0200 - 10002000 - 10000

3.大体流程代码实现(程序框架)

首先,我们需要从文件中读取数据,进行初始化操作,这里可能会需要以下函数:

private void init();
    |
    | --- private List<City> getData(); //从文件中读取信息
    |
    | --- private void initEntities(); //初始化群体
    ·       |
    ·       | --- private boolean isExist(); //判断个体是否已经出现过
    ·       | 
    ·       | --- private void calLength(); //计算个体的路径长度
    ·       |
    ·       | --- private void setReproducePercent(); //计算个体轮盘赌策略中的概率

做好初始化工作之后,就可以开始进行繁殖操作了,可能会用到以下函数:

private void genetic(); //一次繁殖操作
    |
    | --- private void hybridization(); //一次杂交操作
    ·       |
    ·       | --- private Entity getEntity(); //从父代中选取一个个体(轮盘赌策略 - 选择操作)
    ·       |
    ·       | --- private void swapAndHandleConflict(); //交换基因并解决冲突(交叉操作)
    ·
    |
    | --- private void variable(); //变异子代(变异操作)
    |
    | --- private void updateEntity(); //精英保留,更新父代信息
    ·       | 
    ·       | --- this.setReproducePercent(); //对新一代父代重新计算其个体参与轮盘赌策略的概率
    |
    | --- private void choose(); //选取记录最优个体

这就是整个流程的大体框架,程序设计风格因人而异,可能有些人不喜欢这样的设计方式,不过还是推荐大家先构思好程序的框架再编写逻辑代码。

接下来就解决两个较难理解的点:

  1. 轮盘赌策略
  2. 冲突解决
轮盘赌策略

轮盘赌策略图解

轮盘赌策略是一个累加选取的过程:

首先,我们维护一个累加概率p,将其初始化为0.0:p = 0.0。依次遍历每个节点 A - E,A加上累加概率p的值为12.5% + 0 = 12.5%:12.5% < 37.5%;目前未达到目标值 37.5%,所以我们接着向下遍历,B加上累加概率p的值为 12.5% + 12.5% = 25%:25% < 37.5%;continue;C加上累加概率p的值为 25% + 37.5% = 62.5%:62.5% > 37.5%。到此为止,我们的累加概率为 62.5%,已经满足了选择概率(即大于选择概率),所以我们就选择节点C。这就是一次完整的轮盘赌策略的选择。

在TSP问题中,我们需要得到每个个体的选择概率(也就是当前个体在总个体中被选择的概率)。如前文所说,为了保证满足总路径越短的个体适应度越高,我们使用路径的倒数作为适应度。而现在,为了保证满足总路径越短的个体被挑选则概率越大,我们也可以使用路径的倒数,在对其进行归一化处理之后,作为其选择概率。

归一化处理,指的就是将所有个体的适应度求和作为“单位1”,然后就可以计算出每个个体的选择概率,也就是个体适应度在“单位一”中所占的比重。例如现在有三个个体 A、B、C,其适应度分别为a、b、c,“单位一”就是 a+b+c,而个体 A 的选择概率就是 a/(a+b+c),个体 B、C 的选择概率依次类推。

冲突解决

现在,我们假设有两个染色体:

染色体R1 : 1 2 3 | 4 5 6 | 7 8

染色体R2 : 5 1 8 | 3 4 7 | 6 2

现在选择第四个到第六个间的基因片段(‘ | ’之内的片段)进行交叉互换,互换完后染色体分别为:

染色体R1 : 1 2 3 | 3 4 7 | 7 8

染色体R2 : 5 1 8 | 4 5 6 | 6 2

对于R1来说,‘3’、‘7’出现了不止一次,对于R2来说,‘5’、‘6’出现了不止一次。这就出现了冲突。也就是说冲突就是交换基因片段外(下文简称为“片段外”)中存在着交换基因片段内(下文简称为“片段内”)的基因,也就是说冲突存在的地方是片段外。例如:对于R1,片段外(1 2 3 7 8 )中存在着片段内(3 4 7)中的基因‘3’和基因‘7’。
所以欲解决冲突,我们需要将片段外的重复基因替换为没有出现过的基因,以下提供两种解决冲突的方法:

方法一:

对于R1中的重复基因‘3’而言:重复基因‘3’在片段内对应的R2的基因为‘4’,所以我们将R1片段外的重复基因‘3’换成基因‘4’,而我们发现R1片段内存在基因‘4’,且基因‘4’对应着R2的基因‘5’,所以我们接着把刚刚换出的基因‘4’换成基因‘5’,因为基因‘5’并未在R1片段内出现,所以本次交换完毕,向后遍历找到下一个重复基因并完成类似操作。

图解解决冲突操作
交叉互换后:
R11 2 3 | 3 4 7 | 7 8  // 片段内'3'对应'4'
              |       
R25 1 8 | 4 5 6 | 6 2
第一次对 ‘3’ 进行操作后:
R11 2 4 | 3 4 7 | 7 8  // '3' 换成了 '4' - 该基因仍存在冲突, 片段内 '4' 对应 '5'
                |       
R25 1 8 | 4 5 6 | 6 2
第二次对 ‘3’(也就是 ‘4’ ) 进行操作后:
R11 2 5 | 3 4 7 | 7 8  // '4' 换成了 '5' - 该基因冲突解决完毕

R25 1 8 | 4 5 6 | 6 2

找到下一个冲突基因并以相同的方法解决冲突…

方法二:

对于R1, 我们可以发现其缺少了基因‘5’‘6’,遍历R1,我们可以发现基因‘3’和基因‘7’是重复基因,所以我们把‘3’和‘7’换成‘5’和‘6’就行。

图解解决冲突操作
交叉互换后:
          ^           ^
R1 : 1 2 3 | 3 4 7 | 7 8  // '1 2 3 4 5 6 7 8' - '1 2 3 4 7 8' = '5 6'

R2 : 5 1 8 | 4 5 6 | 6 2
修改重复基因 ‘3’ ‘7’ 后:
R11 2 5 | 3 4 7 | 6 8  // '3' -> '5'   '7' -> '6' 该染色体冲突解决  

R25 1 8 | 4 5 6 | 6 2

看到这儿,有人不禁会问了,有没有一种可以实现0冲突的交叉方法?方法当然是有的,如下:

0冲突方法:

对于交换完的两个染色体:

R1 : _ _ _ | 3 4 7 | _ _  // 横线上依次应是 '1 2 3 7 8' 

R2 : _ _ _ | 4 5 6 | _ _  // 横线上依次应是 '5 1 8 6 2'

现在我们把R1横线上应存在的数倒序放到R2的横线上(R2同理)。因此,R1、R2就成了这个样子:

R1 : 2 6 8 | 3 4 7 | 1 5

R2 : 8 7 3 | 4 5 6 | 2 1

是不是可以惊奇的发现。冲突并不存在,因为R1、R2实际上就是保持片段内基因顺序不变,把剩余基因倒序重新排放了,所以当然不会产生冲突!至于该方法是否符合你所认为的“交叉互换”就仁者见仁智者见智了。不过在我看来,子代保留了父代的相关基因(片段内基因)并有属于自己的基因(倒序的基因)就已经算得上是一次真正的繁殖了。

下面我们就开始填充框架,完成核心代码的实现。

4.相关数据结构和关键功能具体实现

为了方便对于个体染色体的基因进行一系列的操作,我们维护了一个List<基因>用于存放基因的相关信息,而在其余任何地方对基因对象class的操作都转化为对基因编号int的操作以避免操作对象时可能出现的一系列问题(引用赋值的弊端)。

(1) 数据结构

个体:
个体由染色体唯一辨别。在TSP问题中,染色体就是走完所有城市所产生的一个城市序列。每个个体的染色体(城市序列)确定了其路径长度(适应度)。

/**
 * 个体类
 *  包含 :
 *      1. 个体的基因序列(城市序列,为避免对象操作,只存储城市序号)
 *      2. 路径长度
 *      3. 适应度
 */
class Entity {

    public List<Integer> cities;  //个体的城市有序序列-染色体
    public double totalLength; //个体的路径总长度-倒数为适应度
    public double reproducePercent; //个体参与繁殖的概率-参与轮盘赌策略的值

}

基因:
个体的染色体由基因组成。在TSP问题中,每个基因就是一个城市节点,每个节点至少存储了该城市的坐标信息。

/**
 * 城市类
 *  包含 :
 *      1. 城市编号(从1开始)
 *      2. 城市横纵坐标
 */
class City {

    public int no; //序号
    public int x;  //横坐标
    public int y;  //纵坐标

}
(2) 关键功能
相关常数设定
private final double variable_percent = 0.1; //变异概率
private final double hybridization_percent = 1.0; //杂交概率
private final int inheritance_number = 10000; //遗传次数

private final int city_number = 48; //城市数量
private final int entity_number = 1000; //个体数量
private final String data_path = "[FILE_PATH]"; //数据集文件 att48 - 10628
private final String data_separator = " "; //数据分隔符
初始化相关

初始化函数:

/**
 * 初始化函数
 * 根据城市的数量初始化城市节点 - 从data中读取每个城市的坐标,封装到一个城市实体中
 * 根据个体数量初始化个体 - 建立一个城市list, 每次shuffle, 并保证没有相同的个体存在
 * @throws IOException 文件读取异常
 */
private void init() throws IOException {
    List<City> cityList = this.readData(); //读取所有城市信息
    this.cityInfo = new ArrayList<>();
    this.cityInfo.addAll(cityList); //保存所有城市信息
    List<Integer> cityNo = new ArrayList<>();
    for(int i = 0; i < cityList.size(); ++i) {
        cityNo.add(cityList.get(i).no); //对城市操作时,只操作城市的序号 (int方便操作-比较/替换)
    }
    this.initEntities(cityNo); //初始化个体
}

读取文件函数:

/**
 * 从文件中读取城市数据
 */
private List<City> readData() throws IOException {
    List<City> cityList = new ArrayList<>();
    BufferedReader br = new BufferedReader(new FileReader(new File(this.data_path)));
    String s;
    while((s = br.readLine()) != null) { //数据为 '序号 横坐标 纵坐标' - 上文已给出 数据分隔符为' '
        int no = Integer.valueOf(s.split(this.data_separator)[0]);
        int x = Integer.valueOf(s.split(this.data_separator)[1]);
        int y = Integer.valueOf(s.split(this.data_separator)[2]);
        cityList.add(new City(no, x, y));
    }
    return cityList;
}

初始化个体函数:
为了避免对象操作,我在组建个体染色体存放城市节点时,只存放城市的编号,并且维护了一个存放了城市所有信息的List<City>,所以通过城市的编号i就可以通过list.get(i - 1)来得到城市的所有信息。

/**
 * 初始化个体, 初始化每个个体的城市列表, 设置路径长度等
 * @param cityNo 所有城市列表(仅有城市编号)
 */
private void initEntities(List<Integer> cityNo) {
    this.entities = new Entity[this.entity_number];
    for(int i = 0; i < this.entities.length; ++i) {
        Collections.shuffle(cityNo); //随机打乱序列
        while(isExist(cityNo, entities)) { //保证个体不重复
            Collections.shuffle(cityNo);
        }
        this.entities[i] = new Entity(cityNo); //new个体
        this.entities[i].totalLength = this.calLength(cityNo); //计算路径长度
    }
    this.setReproducePercent(); //计算参与轮盘赌选择的概率
}

/**
 * 判断城市序列是否存在于一个个体数组中(个体由染色体也就是城市序列唯一辨别)
 * @param randomCity 随机的城市列表
 * @param entities 个体列表(城市列表为个体的属性)
 * @return true 存在/ false 不存在
 */
private boolean isExist(List<Integer> randomCity, Entity[] entities) {
    for(int i = 0; i < entities.length; ++i) {
        if(entities[i] == null) {
            return false;
        } else {
            List<Integer> target = entities[i].cities;
            for(int j = 0; j < target.size(); ++j) {
                if(randomCity.get(j) != target.get(j)) { //有一位不同即不同
                    return false;
                }
            }
            return true; //遍历完 全都相同即相同
        }
    }
    return false;
}

/**
 * 计算个体的路径距离
 * @param cityNo 个体的城市列表
 * @return double-距离
 */
private double calLength(List<Integer> cityNo) {
    double totalLength = 0.0;
    for(int i = 0; i < cityNo.size(); ++i) {
        if(i == 0) {
            totalLength += this.getDistance(cityNo.get(0), cityNo.get(cityNo.size() - 1)); //第一个到最后一个城市两点间的距离
        } else {
            totalLength += this.getDistance(cityNo.get(i), cityNo.get(i - 1)); //当前一个到前面一个城市两点间的距离
        }
    }
    return totalLength;
}

/**
 * 设置每个个体被选择繁殖的概率(根据路径长短归一化)
 * 例如有三个适应度 a b c 
 * 则 a 被选择的概率为 a / (a + b + c), 以此类推...
 */
private void setReproducePercent() {
    double sumLengthToOne = 0.0;
    for(Entity entity : this.entities) {
        sumLengthToOne += 1 / entity.totalLength;
    }
    for(Entity entity : this.entities) {
        entity.reproducePercent = (1 / entity.totalLength) / sumLengthToOne;
    }
}
交叉相关函数

交叉之前要进行选择操作,也就是通过轮盘赌策略选择将参加交叉的父母双亲。我的选择操作体现在getEntity(),每次进行了选择操作之后再进行交叉操作。当然也可以进行完所有的选择操作之后再进行交叉操作。并且,交叉的时候要保证父母双亲不是同一个体,不发生自交。

/**
 * 一次杂交
 *      1. 选取父母
 *      2. 选取交换片段
 *      3. 解决冲突
 */
private void oneHybridization() {
    Entity father = this.getEntity(); //父亲 选择操作
    Entity mother = this.getEntity(); //母亲 选择操作
    Entity best = this.getBestParent(); //最优个体不参与交叉互换 - 个人偏好(可有可无)
    while(father == null || father.equals(best)) {
        father = this.getEntity();
    }
    int cnt = 0;
    while(mother == null || father.equals(mother) || mother.equals(best)) {
        if(cnt > this.entity_number / 2) {
            break;
        }
        cnt++;
        mother = this.getEntity(); //直到父母不等,保证不发生自交
    }
    Random random = new Random(); //随机长度基因交换
    int crossStartIndex = random.nextInt(this.city_number - 1) + 1; //基因交换起始点
    int crossEndIndex = random.nextInt(this.city_number - 1) + 1; //基因交换截止点
    while(crossStartIndex == crossEndIndex) {
        crossEndIndex = random.nextInt(this.city_number - 1) + 1; //起止不相等
    }
    if(crossStartIndex > crossEndIndex) { //交换开始结束, 使结束>开始
        int temp = crossStartIndex;
        crossStartIndex = crossEndIndex;
        crossEndIndex = temp;
    }
    Entity[] newEntity = this.swapAndNoConflict(father, mother, crossStartIndex, crossEndIndex); //儿子个体 - 采取0冲突方法
    for(Entity entity : newEntity) {
        if(entity != null && !isExist(entity.cities, this.entityListToEntityArray(this.sonEntities)) && !isExist(entity.cities, this.entities)) { //去除重复儿子个体
            this.sonEntities.add(entity); //添加到儿子列表
        }
    }
}

/**
 * 轮转赌法获取一个个体并返回(归一体现在reproducePercent)
 * @return 个体
 */
private Entity getEntity() {
    Random random = new Random();
    double selectPercent = random.nextDouble();
    double distributionPercent = 0.0;
    for(int i = 0; i < this.entities.length; ++i) {
        distributionPercent += this.entities[i].reproducePercent;  // 累加选择
        if(distributionPercent > selectPercent) {
            return this.entities[i];
        }
    }
    return null;
}

/**
 * 获取当前entities最优个体
 * 本方法为了上文所述的保留最优个体不参与交叉操作直接复制遗传到下一代
 * @return 最优个体
 */
private Entity getBestParent() {
    if(this.bestEntity == null) {
        Entity bestParent = this.entities[0].clone();
        for(int i = 1; i < this.entities.length; ++i) {
            if(this.entities[i].totalLength < bestParent.totalLength) {
                bestParent = this.entities[i].clone();
            }
        }
        return bestParent;
    } else {
        return this.bestEntity.clone();
    }
}

交叉操作 - 产生冲突的交换方式:

/**
 * 交换基因段并且解决冲突生成新的两个子个体
 * @param father 父亲个体
 * @param mother 母亲个体
 * @param startIndex 交换起始位置
 * @param endIndex 交换截止位置
 * @return 两个个体
 */
private Entity[] swapAndHandleConflict(Entity father, Entity mother, int startIndex, int endIndex) {
    Entity[] newEntities = new Entity[2]; //最多两个儿子
    Entity fatherClone = father.clone(); //父母交换基因段得到儿子, 所以用克隆体
    Entity motherClone = mother.clone();
    Map<Integer, Integer> fatherCityRelation = new HashMap<>();
    Map<Integer, Integer> motherCityRelation = new HashMap<>();
    for(int i = 0; i < this.city_number; ++i) {
        if(i >= startIndex && i <= endIndex) {
            int temp = fatherClone.cities.get(i);
            fatherClone.cities.set(i, motherClone.cities.get(i));
            motherClone.cities.set(i, temp); //交换
            fatherCityRelation.put(fatherClone.cities.get(i), motherClone.cities.get(i)); //获取对应关系, 用来解决冲突
            motherCityRelation.put(motherClone.cities.get(i), fatherClone.cities.get(i)); //父母冲突对应关系恰好相反(偷懒-增加了空间消耗)
        }
    }
    this.handleConflict(fatherClone, fatherCityRelation, startIndex, endIndex);
    this.handleConflict(motherClone, motherCityRelation, startIndex, endIndex);
    newEntities[0] = fatherClone;
    newEntities[1] = motherClone;
    return newEntities;
}

/**
 * 解决冲突, 把entity.cities中不在start-end区间内的city换成cityRelation中相同key对应的value
 * @param entity 可能存在冲突的个体
 * @param cityRelation 冲突对应关系
 * @param startIndex 起始位置
 * @param endIndex 截止位置
 */
private void handleConflict(Entity entity, Map<Integer, Integer> cityRelation, int startIndex, int endIndex) {
    while(conflictExist(entity, cityRelation, startIndex, endIndex)) {
        for(int i = 0; i < entity.cities.size(); ++i) {
            if(i < startIndex || i > endIndex) {
                int temp = entity.cities.get(i);
                if(cityRelation.containsKey(temp)) {
                    entity.cities.set(i, cityRelation.get(temp));
                }
            }
        }
    }
}

/**
 * 是否存在冲突, 即entity.cities中是否出现过cityRelation中keySet中的任一元素
 * @param entity 可能存在冲突的实体
 * @param cityRelation 冲突对应关系
 * @param startIndex 起始位置
 * @param endIndex 截止位置
 * @return true-存在 / false-不存在
 */
private boolean conflictExist(Entity entity, Map<Integer,Integer> cityRelation, int startIndex, int endIndex) {
    for(int i = 0; i < entity.cities.size(); ++i) {
        if(i < startIndex || i > endIndex) {
            if(cityRelation.containsKey(entity.cities.get(i))) {
                return true;
            }
        }
    }
    return false;
}

交叉操作 - 0冲突交换方式:

/**
 * 另一种交叉方法, 避免了冲突
 * 从过程来看 -> 更优的方法
 * 1 4 6 | 2 3 5 | 8 7  ->  3 6 8 | 1 5 4 | 7 2 实际上就是保持 _ _ _ | 1 5 4 | _ _ 顺序不便变, 将 2 7 8 6 3 倒序放入横线上, 即 3 6 8 1 5 4 7 2
 * 2 7 8 | 1 5 4 | 6 3  ->  7 8 6 | 2 3 5 | 4 1
 * @param father 父亲
 * @param mother 母亲
 * @param startIndex 开始位置
 * @param endIndex  截止位置
 * @return 子代数组
 */
private Entity[] swapAndNoConflict(Entity father, Entity mother, int startIndex, int endIndex) {
    Entity[] newEntities = new Entity[2];
    Entity fatherClone = father.clone();
    Entity motherClone = mother.clone();
    List<Integer> fatherCities = new ArrayList<>();
    List<Integer> motherCities = new ArrayList<>();
    fatherCities.addAll(fatherClone.cities);
    motherCities.addAll(motherClone.cities);
    int rightIndex = this.city_number - 1;
    for(int i = 0; i < father.cities.size(); ++i) {
        if(i < startIndex || i > endIndex) {
            while(rightIndex >= startIndex && rightIndex <= endIndex ) {
                rightIndex--;
            }
            fatherClone.cities.set(i, motherCities.get(rightIndex));
            motherClone.cities.set(i, fatherCities.get(rightIndex));
            rightIndex--;
        } else {
            int temp = fatherClone.cities.get(i);
            fatherClone.cities.set(i, motherClone.cities.get(i));
            motherClone.cities.set(i, temp);
        }
    }
    newEntities[0] = fatherClone;
    newEntities[1] = motherClone;
    return newEntities;
}
变异相关函数
/**
 * 变异个体
 *      1. 选择变异节点
 *      2. 交换
 *      3. 检查是否重复
 * @param entity 待变异个体
 */
private void oneVariable(Entity entity) {
    Random random = new Random();
    int index1 = random.nextInt(this.city_number - 1) + 1;
    int index2 = random.nextInt(this.city_number - 1) + 1;
    while(index1 == index2) {
        index2 = random.nextInt(this.city_number); //保证突变基因不是同一个
    }
    int temp = entity.cities.get(index1);
    entity.cities.set(index1, entity.cities.get(index2));
    entity.cities.set(index2, temp);
}
择优相关函数

综合父代子代选取最优个体更新父代后重新计算每个个体的适应度,以参与下一次繁殖操作。

/**
 * 选择优质个体
 *      1. 对所有个体计算路径长短
 *      2. 排序
 *      3. 选择较优个体
 */
private void updateEntity() {
    for(Entity entity : this.sonEntities) { //计算所有子代路径长短
        double length = 0.0;
        List<Integer> city = entity.cities;
        for(int j = 0; j < city.size(); ++j) {
            if (j == 0) {
                length += this.getDistance(city.get(city.size() - 1), city.get(0));
            } else {
                length += this.getDistance(city.get(j), city.get(j - 1));
            }
        }
        entity.totalLength = length;
    }
    List<Entity> allEntities = new ArrayList<>();
    allEntities.addAll(this.sonEntities);
    allEntities.addAll(this.entityArrayToEntityList(this.entities));
    Collections.sort(allEntities); //排序所有个体
    List<Entity> bestEntities = new ArrayList<>();
    for(int i = 0; i < this.entity_number; ++i) {
        bestEntities.add(allEntities.get(i).clone());
    }
    Collections.shuffle(bestEntities);
    for(int i = 0; i < this.entities.length; ++i) {
        this.entities[i] = bestEntities.get(i); //选择
    }
    this.setReproducePercent(); //重新设置选择概率(归一)
}
选择最优个体保存-评估函数
/**
 * 选择最优解, 评估函数
 */
private void choose(int geneticNumber) {
    for(int i = 0; i < this.entities.length; ++i) {
        if(this.entities[i].totalLength < this.minLength) {
            this.bestEntity = this.entities[i].clone();
            this.bestEntity.reproducePercent = this.entities[i].reproducePercent;
            this.minLength = this.entities[i].totalLength;
            this.bestInheritance = geneticNumber;
        }
    }
}
全部代码以及数据集
代码
import java.io.*;
import java.util.*;
import java.util.List;

/**
 * use GA to solve TSP.
 */
public class TSP {

    private final double variable_percent = 0.1; //变异概率
    private final double hybridization_percent = 1.0; //杂交概率
    private final int inheritance_number = 10000; //遗传次数

    private final int city_number = 48; //城市数量
    private final int entity_number = 1000; //个体数量
    private final String data_path = "[FILE_PATH]"; //数据集文件 att48 - 10628
    private final String data_separator = " "; //数据分隔符

    private Entity[] entities; //个体集合 个体包含城市列表(序号),路径长度,适应度
    private List<Entity> sonEntities; //子代集合 内容同个体

    private List<City> cityInfo = null; //所有城市信息,仅此处存城市信息

    private double minLength = Double.MAX_VALUE; //最短路径
    private Entity bestEntity = null; //最佳个体
    private int bestInheritance = 0; //最佳个体出现代数

    public static void main(String[] args) {
        try {
            TSP tsp = new TSP();
            tsp.init(); //初始化数据
            System.out.println("初始数据为 : ");
            for(int i = 0; i < tsp.entities.length; ++i) {
                System.out.println(tsp.entities[i].toString() + " totalLength -> " + tsp.entities[i].totalLength + " reproducePercent -> " + tsp.entities[i].reproducePercent); //数据展示
            }
            tsp.genetic(); //开始遗传
            System.out.println("整体最优解为 : "
                    + "\n    出现代数 : " + tsp.bestInheritance
                    + "\n    路径长度 : " + (tsp.minLength / Math.sqrt(10.0))
                    + "\n    适应度 : " + tsp.bestEntity.reproducePercent
                    + "\n    路径 : " + tsp.bestEntity.toString()); //结果展示
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化函数
     * 根据城市的数量初始化城市节点 - 从data中读取每个城市的坐标,封装到一个城市实体中
     * 根据个体数量初始化个体 - 建立一个城市list, 每次shuffle, 并保证没有相同的个体存在
     * @throws IOException 文件读取异常
     */
    private void init() throws IOException {
        List<City> cityList = this.readData(); //读取所有城市信息
        this.cityInfo = new ArrayList<>();
        this.cityInfo.addAll(cityList); //保存所有城市信息
        List<Integer> cityNo = new ArrayList<>();
        for(int i = 0; i < cityList.size(); ++i) {
            cityNo.add(cityList.get(i).no); //对城市操作时,只操作城市的序号 (int方便操作-比较/替换)
        }
        this.initEntities(cityNo); //初始化个体
    }

    /**
     * 从文件中读取城市数据
     */
    private List<City> readData() throws IOException {
        List<City> cityList = new ArrayList<>();
        BufferedReader br = new BufferedReader(new FileReader(new File(this.data_path)));
        String s;
        while((s = br.readLine()) != null) {
            int no = Integer.valueOf(s.split(this.data_separator)[0]);
            int x = Integer.valueOf(s.split(this.data_separator)[1]);
            int y = Integer.valueOf(s.split(this.data_separator)[2]);
            cityList.add(new City(no, x, y));
        }
        return cityList;
    }

    /**
     * 初始化个体, 初始化每个个体的城市列表, 设置路径长度, 设置适应度
     * @param cityNo 所有城市列表
     */
    private void initEntities(List<Integer> cityNo) {
        this.entities = new Entity[this.entity_number];
        for(int i = 0; i < this.entities.length; ++i) {
            Collections.shuffle(cityNo);
            while(isExist(cityNo, entities)) {
                Collections.shuffle(cityNo);
            }
            this.entities[i] = new Entity(cityNo);
            this.entities[i].totalLength = this.calLength(cityNo);
        }
        this.setReproducePercent();
    }

    /**
     * 判断城市顺序列表是否存在于一个实体数组中(实体属性)
     * @param randomCity 随机的城市列表
     * @param entities 个体列表(城市列表为个体的属性)
     * @return true 存在/ false 不存在
     */
    private boolean isExist(List<Integer> randomCity, Entity[] entities) {
        for(int i = 0; i < entities.length; ++i) {
            if(entities[i] == null) {
                return false;
            } else {
                List<Integer> target = entities[i].cities;
                for(int j = 0; j < target.size(); ++j) {
                    if(randomCity.get(j) != target.get(j)) { //有一位不同即不同
                        return false;
                    }
                }
                return true; //遍历完 全都相同即相同
            }
        }
        return false;
    }

    /**
     * 计算个体的路径距离
     * @param cityNo 个体的城市列表
     * @return double-距离
     */
    private double calLength(List<Integer> cityNo) {
        double totalLength = 0.0;
        for(int i = 0; i < cityNo.size(); ++i) {
            if(i == 0) {
                totalLength += this.getDistance(cityNo.get(0), cityNo.get(cityNo.size() - 1)); //第一个到最后一个
            } else {
                totalLength += this.getDistance(cityNo.get(i), cityNo.get(i - 1)); //当前一个到前面一个
            }
        }
        return totalLength;
    }

    /**
     * 获取两点之间的距离
     * @param from 点1
     * @param to 点2
     * @return 距离
     */
    private double getDistance(int from, int to) {
        int x1 = this.cityInfo.get(from - 1).x;
        int y1 = this.cityInfo.get(from - 1).y;
        int x2 = this.cityInfo.get(to - 1).x;
        int y2 = this.cityInfo.get(to - 1).y;
        return Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2));
    }

    /**
     * 设置每个个体被选择繁殖的概率(根据路径长短归一化)
     */
    private void setReproducePercent() {
        double sumLengthToOne = 0.0;
        for(Entity entity : this.entities) {
            sumLengthToOne += 1 / entity.totalLength;
        }
        for(Entity entity : this.entities) {
            entity.reproducePercent = (1 / entity.totalLength) / sumLengthToOne;
        }
    }


    /**
     * 开始遗传
     * 在迭代次数内执行遗传操作
     */
    private void genetic() {
        for(int i = 0; i < this.inheritance_number; ++i) {
            System.out.println("第" + (i + 1) + "次遗传 ");
            this.oneGenetic(i + 1); //遗传一次
        }
    }

    /**
     * 一次遗传
     *      1. 杂交 选择父母生成子代(交叉互换)
     *      2. 变异 子代基因突变(交换城市位置)
     *      3. 选择 择优存货
     *      4. 择优 选择最优解(最短路径)
     */
    private void oneGenetic(int geneticNumber) {
        this.sonEntities = new ArrayList<>();
//        System.out.println("\t杂交开始");
        this.hybridization(); //杂交
//        System.out.println("\t杂交完成");
        this.variableOnce(); //变异一次
//        this.variableMore(); //变异count次
//        System.out.println("\t变异完成");
        this.updateEntity();  //选择
//        System.out.println("\t选择完成");
        this.choose(geneticNumber); //择优
//        System.out.println("\t择优完成");
    }

    /**
     * 一定次数的杂交
     */
    private void hybridization() {
        Random random = new Random();
        for(int i = 0; i < this.entity_number/ 2 + 1; ++i) { //杂交生成子代总数为 entity_number
            double percent = random.nextDouble();
            while(percent > this.hybridization_percent) { //随机数大于杂交概率就再次随机,保证杂交次数 - 鸡肋
                percent = random.nextDouble();
            }
            this.oneHybridization(); //一次杂交
        }
    }

    /**
     * 一次杂交
     *      1. 选取父母
     *      2. 选取交换片段
     *      3. 解决冲突
     */
    private void oneHybridization() {
        Entity father = this.getEntity(); //父亲
        Entity mother = this.getEntity(); //母亲
        Entity best = this.getBestParent(); //最优个体不参与交叉互换
        while(father == null || father.equals(best)) {
            father = this.getEntity();
        }
        int cnt = 0;
        while(mother == null || father.equals(mother) || mother.equals(best)) {
            if(cnt > this.entity_number / 2) {
                break;
            }
            cnt++;
            mother = this.getEntity(); //直到父母不等
        }
        Random random = new Random(); //随机长度基因交换
        int crossStartIndex = random.nextInt(this.city_number - 1) + 1; //基因交换起始点
        int crossEndIndex = random.nextInt(this.city_number - 1) + 1; //基因交换截止点
        while(crossStartIndex == crossEndIndex) {
            crossEndIndex = random.nextInt(this.city_number - 1) + 1; //起止不相等
        }
        if(crossStartIndex > crossEndIndex) { //交换开始结束, 使结束>开始
            int temp = crossStartIndex;
            crossStartIndex = crossEndIndex;
            crossEndIndex = temp;
        }
        Entity[] newEntity = this.swapAndNoConflict(father, mother, crossStartIndex, crossEndIndex); //儿子个体 - 变化更大
        for(Entity entity : newEntity) {
            if(entity != null && !isExist(entity.cities, this.entityListToEntityArray(this.sonEntities)) && !isExist(entity.cities, this.entities)) { //去除重复个体
                this.sonEntities.add(entity); //添加到儿子列表
            }
        }
    }

    /**
     * 轮转赌法获取一个个体并返回(归一体现在reproducePercent)
     * @return 个体
     */
    private Entity getEntity() {
        Random random = new Random();
        double selectPercent = random.nextDouble();
        double distributionPercent = 0.0;
        for(int i = 0; i < this.entities.length; ++i) {
            distributionPercent += this.entities[i].reproducePercent;
            if(distributionPercent > selectPercent) {
                return this.entities[i];
            }
        }
        return null;
    }

    /**
     * 获取当前entities最优个体
     * @return 最优个体
     */
    private Entity getBestParent() {
        if(this.bestEntity == null) {
            Entity bestParent = this.entities[0].clone();
            for(int i = 1; i < this.entities.length; ++i) {
                if(this.entities[i].totalLength < bestParent.totalLength) {
                    bestParent = this.entities[i].clone();
                }
            }
            return bestParent;
        } else {
            return this.bestEntity.clone();
        }
    }

    /**
     * 交换基因段并且解决冲突生成新的两个子个体
     * @param father 父亲个体
     * @param mother 母亲个体
     * @param startIndex 交换起始位置
     * @param endIndex 交换截止位置
     * @return 两个个体
     */
    private Entity[] swapAndHandleConflict(Entity father, Entity mother, int startIndex, int endIndex) {
        Entity[] newEntities = new Entity[2]; //最多两个儿子
        Entity fatherClone = father.clone(); //父母交换基因段得到儿子, 所以用克隆体
        Entity motherClone = mother.clone();
        Map<Integer, Integer> fatherCityRelation = new HashMap<>();
        Map<Integer, Integer> motherCityRelation = new HashMap<>();
        for(int i = 0; i < this.city_number; ++i) {
            if(i >= startIndex && i <= endIndex) {
                int temp = fatherClone.cities.get(i);
                fatherClone.cities.set(i, motherClone.cities.get(i));
                motherClone.cities.set(i, temp); //交换
                fatherCityRelation.put(fatherClone.cities.get(i), motherClone.cities.get(i)); //获取对应关系, 用来解决冲突
                motherCityRelation.put(motherClone.cities.get(i), fatherClone.cities.get(i)); //父母冲突对应关系恰好相反(偷懒)
            }
        }
        this.handleConflict(fatherClone, fatherCityRelation, startIndex, endIndex);
        this.handleConflict(motherClone, motherCityRelation, startIndex, endIndex);
        newEntities[0] = fatherClone;
        newEntities[1] = motherClone;
        return newEntities;
    }

    /**
     * 解决冲突, 把entity.cities中不在start-end区间内的city换成cityRelation中相同key对应的value
     * @param entity 可能存在冲突的个体
     * @param cityRelation 冲突对应关系
     * @param startIndex 起始位置
     * @param endIndex 截止位置
     */
    private void handleConflict(Entity entity, Map<Integer, Integer> cityRelation, int startIndex, int endIndex) {
        while(conflictExist(entity, cityRelation, startIndex, endIndex)) {
            for(int i = 0; i < entity.cities.size(); ++i) {
                if(i < startIndex || i > endIndex) {
                    int temp = entity.cities.get(i);
                    if(cityRelation.containsKey(temp)) {
                        entity.cities.set(i, cityRelation.get(temp));
                    }
                }
            }
        }
    }

    /**
     * 是否存在冲突, 即entity.cities中是否出现过cityRelation中keySet中的任一元素
     * @param entity 可能存在冲突的实体
     * @param cityRelation 冲突对应关系
     * @param startIndex 起始位置
     * @param endIndex 截止位置
     * @return true-存在 / false-不存在
     */
    private boolean conflictExist(Entity entity, Map<Integer,Integer> cityRelation, int startIndex, int endIndex) {
        for(int i = 0; i < entity.cities.size(); ++i) {
            if(i < startIndex || i > endIndex) {
                if(cityRelation.containsKey(entity.cities.get(i))) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 另一种交叉方法, 避免了冲突
     * 从过程来看 -> 更优的方法
     * 1 4 6 | 2 3 5 | 8 7  ->  3 6 8 | 1 5 4 | 7 2 实际上就是保持 _ _ _ | 1 5 4 | _ _ 顺序不便变, 将 2 7 8 6 3 倒序放入横线上, 即 3 6 8 1 5 4 7 2
     * 2 7 8 | 1 5 4 | 6 3  ->  7 8 6 | 2 3 5 | 4 1
     * @param father 父亲
     * @param mother 母亲
     * @param startIndex 开始位置
     * @param endIndex  截止位置
     * @return 子代数组
     */
    private Entity[] swapAndNoConflict(Entity father, Entity mother, int startIndex, int endIndex) {
        Entity[] newEntities = new Entity[2];
        Entity fatherClone = father.clone();
        Entity motherClone = mother.clone();
        List<Integer> fatherCities = new ArrayList<>();
        List<Integer> motherCities = new ArrayList<>();
        fatherCities.addAll(fatherClone.cities);
        motherCities.addAll(motherClone.cities);
        int rightIndex = this.city_number - 1;
        for(int i = 0; i < father.cities.size(); ++i) {
            if(i < startIndex || i > endIndex) {
                while(rightIndex >= startIndex && rightIndex <= endIndex ) {
                    rightIndex--;
                }
                fatherClone.cities.set(i, motherCities.get(rightIndex));
                motherClone.cities.set(i, fatherCities.get(rightIndex));
                rightIndex--;
            } else {
                int temp = fatherClone.cities.get(i);
                fatherClone.cities.set(i, motherClone.cities.get(i));
                motherClone.cities.set(i, temp);
            }
        }
        newEntities[0] = fatherClone;
        newEntities[1] = motherClone;
        return newEntities;
    }

    /**
     * 把entity列表转换成数组返回
     * @param list 待转换列表
     * @return entity数组
     */
    private Entity[] entityListToEntityArray(List<Entity> list) {
        Entity[] arr = new Entity[list.size()];
        for(int i = 0; i < list.size(); ++i) {
            arr[i] = list.get(i).clone();
        }
        return arr;
    }

    /**
     * 变异子代, 每个子代变异一次
     */
    private void variableOnce() {
        Random random = new Random();
        double percent = random.nextDouble();
        for(Entity entity : this.sonEntities) {
            if(percent < this.variable_percent) {
//                int count = random.nextInt(this.city_number);
//                for(int i = 0; i < count; ++i) { //变异count次
                    this.oneVariable(entity);
//                }
            }
            percent = random.nextDouble();
        }
    }

    /**
     * 变异子代, 每个子代变异count次
     */
    private void variableMore() {
        Random random = new Random();
        double percent = random.nextDouble();
        for(Entity entity : this.sonEntities) {
            if(percent < this.variable_percent) {
                int count = random.nextInt(this.city_number);
                for(int i = 0; i < count; ++i) { //变异count次
                    this.oneVariable(entity);
                }
            }
            percent = random.nextDouble();
        }
    }

    /**
     * 变异个体
     *      1. 选择变异节点
     *      2. 交换
     *      3. 检查是否重复
     * @param entity 待变异个体
     */
    private void oneVariable(Entity entity) {
        Random random = new Random();
        int index1 = random.nextInt(this.city_number - 1) + 1;
        int index2 = random.nextInt(this.city_number - 1) + 1;
        while(index1 == index2) {
            index2 = random.nextInt(this.city_number); //保证突变基因不是同一个
        }
        int temp = entity.cities.get(index1);
        entity.cities.set(index1, entity.cities.get(index2));
        entity.cities.set(index2, temp);
    }

    /**
     * 选择优质个体
     *      1. 对所有个体计算路径长短
     *      2. 排序
     *      3. 选择较优个体
     */
    private void updateEntity() {
        for(Entity entity : this.sonEntities) { //计算所有子代路径长短
            double length = 0.0;
            List<Integer> city = entity.cities;
            for(int j = 0; j < city.size(); ++j) {
                if (j == 0) {
                    length += this.getDistance(city.get(city.size() - 1), city.get(0));
                } else {
                    length += this.getDistance(city.get(j), city.get(j - 1));
                }
            }
            entity.totalLength = length;
        }
        List<Entity> allEntities = new ArrayList<>();
        allEntities.addAll(this.sonEntities);
        allEntities.addAll(this.entityArrayToEntityList(this.entities));
        Collections.sort(allEntities); //排序所有个体
        List<Entity> bestEntities = new ArrayList<>();
        for(int i = 0; i < this.entity_number; ++i) {
            bestEntities.add(allEntities.get(i).clone());
        }
        Collections.shuffle(bestEntities);
        for(int i = 0; i < this.entities.length; ++i) {
            this.entities[i] = bestEntities.get(i); //选择
        }
        this.setReproducePercent(); //重新设置选择概率(归一)
    }

    /**
     * 把entity数组转化成list返回
     * @param entities 待转换数组
     * @return list
     */
    private List<Entity> entityArrayToEntityList(Entity[] entities) {
        List<Entity> list = new ArrayList<>();
        for(Entity entity : entities) {
            list.add(entity.clone());
        }
        return list;
    }

    /**
     * 选择最优解, 评估函数
     */
    private void choose(int geneticNumber) {
        boolean isUpdate = false;
        for(int i = 0; i < this.entities.length; ++i) {
            if(this.entities[i].totalLength < this.minLength) {
                this.bestEntity = this.entities[i].clone();
                this.bestEntity.reproducePercent = this.entities[i].reproducePercent;
                this.minLength = this.entities[i].totalLength;
                this.bestInheritance = geneticNumber;
                isUpdate = true;
            }
        }
        if(isUpdate) {
            System.out.println("当前最优解为 : "
                    + "\n    出现代数 : " + this.bestInheritance
                    + "\n    路径长度 : " + (int)(this.minLength / Math.sqrt(10.0))
                    + "\n    适应度 : " + this.bestEntity.reproducePercent
                    + "\n    路径 : " + this.bestEntity.toString()); //结果展示
        }
    }

}

/**
 * 个体类
 *  包含 :
 *      1. 个体的基因序列(城市序列 避免对象操作,只存储城市no)
 *      2. 路径长度
 *      3. 适应度
 */
class Entity implements Comparable<Entity> {

    public List<Integer> cities;  //个体的城市有序序列
    public double totalLength; //个体的路径总长度
    public double reproducePercent; //个体参与繁殖的概率 适应度

    public Entity(List<Integer> cities) {
        this.cities = new ArrayList<>();
        this.cities.addAll(cities);
    }

    /**
     * 两个个体是否相等-基因列表相同
     * @param entity 对比个体
     * @return true-相同 false-不同
     */
    public boolean equals(Entity entity) {
        return this.citiesEquals(entity.cities);
    }

    /**
     * 判断基因列表是否相同
     * @param anotherCities 对比个体的城市列表
     * @return true-相同 false-不同
     */
    public boolean citiesEquals(List<Integer> anotherCities) {
        for(int i = 0; i < this.cities.size(); ++i) {
            if(this.cities.get(i) != anotherCities.get(i)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 克隆城市列表和长度
     * @return 克隆个体
     */
    @Override
    public Entity clone() {
        List<Integer> list = new ArrayList<>();
        list.addAll(this.cities);
        Entity clone = new Entity(list);
        clone.totalLength = this.totalLength;
        clone.reproducePercent = this.reproducePercent;
        return clone;
    }

    /**
     * 城市列表toString
     * @return 城市列表string
     */
    @Override
    public String toString() {
        return this.cities.toString();
    }

    /**
     * 实现Comparable的方法,实现个体对比
     * @param o 对比个体
     * @return 1-大于 0-等于 (-1)-小于
     */
    @Override
    public int compareTo(Entity o) {
        return this.sign(this.totalLength - o.totalLength);
    }

    /**
     * 符号函数
     * @param x 参数值
     * @return 正数-1 0-0 负数-(-1)
     */
    private int sign(double x) {
        return (x > 0) ? 1 : ((x == 0) ? 0 : -1);
    }
}

/**
 * 城市类
 *  包含 :
 *      1. 城市编号 - data.txt顺序为序号(从1开始)
 *      2. 城市横纵坐标
 */
class City {

    public int no; //编号
    public int x;  //横坐标
    public int y;  //纵坐标

    public City(int no, int x, int y) {
        this.no = no;
        this.x = x;
        this.y = y;
    }

    /**
     * 城市toString
     * @return 城市序号
     */
    @Override
    public String toString() {
        return String.valueOf(this.no);
    }
}
数据集
1 6734 1453
2 2233 10
3 5530 1424
4 401 841
5 3082 1644
6 7608 4458
7 7573 3716
8 7265 1268
9 6898 1885
10 1112 2049
11 5468 2606
12 5989 2873
13 4706 2674
14 4612 2035
15 6347 2683
16 6107 669
17 7611 5184
18 7462 3590
19 7732 4723
20 5900 3561
21 4483 3369
22 6101 1110
23 5199 2182
24 1633 2809
25 4307 2322
26 675 1006
27 7555 4819
28 7541 3981
29 3177 756
30 7352 4506
31 7545 2801
32 3245 3305
33 6426 3173
34 4608 1198
35 23 2216
36 7248 3779
37 7762 4595
38 7392 2244
39 3484 2829
40 6271 2135
41 4985 140
42 1916 1569
43 7280 4899
44 7509 3239
45 10 2676
46 6807 2993
47 5185 3258
48 3023 1942
参阅博客

TSP_旅行商问题 - 遗传算法(四)
http://blog.csdn.net/houchaoqun_xmu/article/details/54584264

遗传算法详解(GA)(个人觉得很形象,很适合初学者)
http://blog.csdn.net/u010451580/article/details/51178225

经典算法研究系列:七、深入浅出遗传算法
https://www.cnblogs.com/v-July-v/archive/2011/01/12/3320879.html

  • 11
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值