TSP问题,又称旅行商问题、货郎问题。
问题描述:再给定的城市地图中,要求旅行商走遍所有的城市,且每一个城市只能去一次,求该旅行商能够走遍所有城市的最短路径。
使用遗传算法解决该问题,遗传算法指通过给定的初始种群,进行有限次的种族迭代,最后使种族中保留个体适应度都是最高的(即适应度高的个体能够再种族中进行繁殖,它们的基因不会被淘汰),适应度高的个体就代表要求的最优解。
本文中个体以字符串形式表示,个体集合使用Map<String,Integer> population,key:表示个体的染色体,value:表示个体的适应度。使用Map集合是因为能够保证种群中的个体染色体唯一。
由上图可以看出,个体适应度值大的个体计算出来的选择概率也就大(选择概率=个体适应度/种群中个体适应度总和),而TSP问题中,个体的优劣是看个体的距离代价是否为最小,因此为了保证距离代价小的个体适应度最大,则定义一个max值(max要足够大,大于距离矩阵中可能出现的最大距离代价的值),个体适应度=max-个体距离代价 。即可保证距离代价小的个体,适应度是大的,最后输出最优解时,在使用max-最优个体适应度,即可算出最小距离代价(最短路径长度)。
在种群迭代的过程中有如下动作:
random_select(Map population):在种群中随机选择一个符合概率要求的个体(本文中选择根据该方法选出的个体将作为父代进行交叉繁殖出子代)
reproduce(String x,String y):根据选出的父代个体,进行交叉繁殖,产生一个子代个体
mutate(String x):根据随机生成的概率p(random(0~1)),(if p < β)则对传入的个体执行变异操作。变异方法为在个体中随机生成两个坐标,坐标取值范围 1~x.length()-1 此处不能够取下标0 是因为,本文方法中,能够指定起点城市,为了保证后续生成的子代,都是以该城市作为起点城市,所以 在变异函数 mutate 和 交叉函数reproduce 中都要保证不改变 字符串第一位的值。
select_Best():在种群中选择适应度最大的个体,作为最优个体(best_individual)。
isRepeat(String x):判断保证个体的染色体没有重复的基因,在TSP问题中,基因指的就是城市的编号,染色体就是行走方式,而每个城市只能去一次,因此需要保证染色体中没有重复的基因。
*注:本文代码无法保证最后的种群中个体都是最优解个体(解决办法还不会,我是菜鸡),因为种群有个变异函数,及时在某一次迭代过程中,种群只剩一个最优个体后,也会因为变异,而导致个体改变。但是因为只要迭代次数够多,最后都能保证最优解在迭代过程中,出现至少一次,而select_best:将该解保存在了best_individual 中,所以程序最后能输出最优解。若要输出所有最优解,则将best_individual 改成Map 集合,然后对其中的内容进行控制即可。
package com.AI.Experiment4;
import java.util.*;
/**
* 遗传算法的结点(一个结点作为一个个体)
*/
class GN_Node {
public String sol; //存放路径
public int value; //存放该路径的距离代价
public GN_Node(int num){
sol="";
value=Integer.MAX_VALUE;
}
}
/**
* 遗传算法 解决TSP问题
*
* 由于遗传算法要求个体适应度大的易于繁殖,而TSP问题求的是最短路径,
* 因此需要保证路径值最短的个体的适应度、选择概率、累积概率是相对大
* 的,因此将个体的适应度 都减去相同的值(该值要足够大。大于所有可
* 能计算出来的路径长度),再取绝对值。最后的最短路径结果,只需要
* 使用该值,减去个体的适应度即可。
*/
public class Genetic_algorithms {
private Map<String,Integer> population; //种群,使用map保存个体(individual),key为个体的基因片段,value为个体的评估值,这样可以保证个体的唯一,一个键值对表示一个个体
private int[][] Dist; //城市的距离矩阵
private int city_num;//城市数量
private float mutate;//个体变异率 (若概率小于该数,则变异个体)
private int max=3000; //最大值
private String best_individual; //最优个体
private int best_distance; //最短路径长度
/**
* 初始化算法执行所需要的各个参数
* @param dist 城市距离矩阵
* @param mutate 个体变异率
* @param start 起点城市
* @param num 初始化种群大小
*/
public Genetic_algorithms(int[][] dist,float mutate,int start,int num){
Dist=dist;
population=new HashMap<>();
city_num=Dist.length;
this.mutate=mutate;
init(start,num);
}
/**
* 在当前种群中,选出适应度最大的作为最优解
* @return
*/
public void select_Best(){
Set<String> strings = population.keySet();
for (String string : strings) {
if(population.get(string)>best_distance){
best_individual=string;
best_distance=population.get(string);
}
}
}
/**
* 初始化初代种群
* @param start 起点城市
* @param num 初代种群大小
*/
public void init(int start,int num){
//生成含有10个不相同个体的初始种群
int i=0;
Random random=new Random();//用于产生 0~city_num 之间的整数,作为城市编号
do{
String individual=""+start; //个体,保证个体基因不重复,TSP问题中,基因指城市编号
for(int j=1;j<city_num;j++){
int gen;
do{
gen=random.nextInt(city_num); //随机生成城市编号
}while(isRepeat(individual,gen));//确保生成的基因不重复
individual+=gen;
}
if(!population.containsKey(individual)){
//若不包含该个体,则将其加入到map集合中
//计算该个体的适应度
int value=evaluate(individual);
population.put(individual,value);
//初始化最优解,将第一个个体作为最优解赋值给 best_individual 和 best_distance
if(i==0){
best_individual=individual;
best_distance=value;
}
i++;
}
}while(i<num);
System.out.println(population);
}
/**
* 用于检查一个基因,在该基因个体基因上是否有重复
* @return
*/
public boolean isRepeat(String individual,int gen){
for(int i=0;i<individual.length();i++){
if(individual.charAt(i)-'0'==gen)
return true;
}
return false;
}
/**
* 实现个体的变异,变异方式为:随机生成两个坐标,x、y x≠y 然后将x和y的基因对换
* @param individual
*/
public String mutate(String individual){
Random random=new Random();
int length=individual.length();
int x,y;
do{
x=random.nextInt(length);
y=random.nextInt(length);
}while(!(x!=0&&y!=0&&x!=y));
char[] chars = individual.toCharArray();
char temp=chars[x];
chars[x]=chars[y];
chars[y]=temp;
String mutate_individual="";
for (int i=0;i<chars.length;i++){
mutate_individual+=chars[i];
}
return mutate_individual;
}
/**
* 根据传入的个体基因,计算该结点的适应度
* @param individual
* @return
*/
public int evaluate(String individual){
int distance=0; //用距离代价作为该个体的适应度
int x;
int y;
for(int i=1;i<individual.length();i++){
x=individual.charAt(i-1)-'0'; //计算出城市编号
y=individual.charAt(i)-'0'; //计算出城市编号
distance=distance+Dist[x][y];
}
x=individual.charAt(city_num-1)-'0';
y=individual.charAt(0)-'0';
distance=distance+Dist[x][y];
return -(distance-max);//由于遗传算法要求个体适应度大的易于繁殖,而TSP问题求的是最短路径,因此给计算出来的距离代价加上-号,保证距离代价小的个体适应度大
}
/**
* 在种群中随机选择个体进行繁殖
* @param population
* @return
*/
public String random_select(Map population){
int sum=0; //保存个体适应度总和,用于求个体的选择概率 选择概率=个体适应度/种群适应度总和
Random random=new Random(); //用于生成随机选择概率
Set<String> set = population.keySet();
for (String s : set) {
sum+=(int)population.get(s);
}
for (String s : set) {
double r=random.nextDouble(); //生成随机选择概率
//若该个体的选择概率大于随机生成的选择概率,则将该个体作为选择出来的个体进行繁殖
double value=((double) ((int)population.get(s))/sum);
// System.out.println(r+","+value+","+(int)population.get(s));
if(r<value){
return s;
}
}
return null;
}
/**
* 传入两个父辈个体,放回其后代
* @param x
* @param y
* @return
*/
public String reproduce(String x,String y){
int n=x.length();
Random random=new Random(); //用于生成随机的分割点,但是不能去0和n-1
int index=0;
do{
index=random.nextInt(n);
}while(index>0&&index<n-1);
return x.substring(0,index)+y.substring(index,n); //返回后代
}
/**
* 判断是否有重复基因
* @param individual
* @return
*/
public boolean isRepeat(String individual){
for(int i=0;i<individual.length();i++){
for(int j=i+1;j<individual.length();j++){
if(individual.charAt(i)==individual.charAt(j))
return true;
}
}
return false;
}
/**
* 遗传算法主函数
*
*/
public void GN_main(){
Random random=new Random();
int num_iteration=0; //计数种群的迭代次数
while(num_iteration<10000){
Map<String,Integer> new_population=new HashMap<>(); //临时存放新产生的种群
//每一次迭代,先在种群中选出最优best_individual
select_Best();
for(int i=0;i<population.size();i++){
//随机选择两个父个体
String parent1;
String parent2;
do {
parent1=random_select(population);
}while (parent1==null);
do {
parent2=random_select(population);
}while (parent2==null);
//两个父个体产生一个子个体,确保子代没有重复的基因
String child;
do {
child=reproduce(parent1,parent2);
}while (isRepeat(child));
float r=random.nextFloat(); //随机产生一个0~1之间的数
if(r<random.nextFloat()){
child=mutate(child);
}
if(!new_population.containsKey(child)){
int value=evaluate(child);
new_population.put(child,value); //将子代加入种群
}
}
//更新种群,将新产生的种群作为当前种群,继续进行下一代繁殖
population=new_population;
/*
此处有一个想法:就是想要在种群中进行一个淘汰机制,将新种群于就种群求并集,然后计算出种群的平均适应度,
再将并集后的种群中,小于平均适应度的个体给淘汰出种群。想要以此保证种群不断收敛到最优个体(即希望种群
中剩余个体都是最优解),但是程序逻辑个人感觉没问题,但是程序报错
(Exception in thread "main" java.util.ConcurrentModificationException)
没想明白为啥报错。。。。。
Set<String> strings = new_population.keySet();
for (String string : strings) {
if (!population.containsKey(string)){
population.put(string,new_population.get(string));
}
}
System.out.println(population);
//淘汰部分种群成员,此处淘汰适应度低于平均值的
Set<String> strings1 = population.keySet();
int sum=0;
for (String s : strings1) {
sum+=population.get(s);
}
sum=sum/population.size();
for (String s : strings1) { //报错位置!!!!!!!!!!!!!!!!
if(population.get(s)<=sum){
population.remove(s);
}
}
*/
num_iteration++;
System.out.println("当前迭代第"+num_iteration+"次。种族为:"+population);
}
}
public String getBest_individual() {
return best_individual;
}
public int getBest_distance() {
return best_distance;
}
public Map<String, Integer> getPopulation() {
return population;
}
public static void main(String[] args) {
int max=655;
//距离矩阵
int[][] Dist={{max,3,2,3,max},{3,max,max,6,4},{2,max,max,1,5},{3,6,1,max,7},{max,4,5,7,max}};
float a=0.98f;
Genetic_algorithms sa=new Genetic_algorithms(Dist,a,0,10);
sa.GN_main();
System.out.println("最佳行驶路径:"+sa.getBest_individual()+".最短路径:"+(3000-sa.getBest_distance()));
}
}
广东省各市区之间的距离矩阵:
*注:由于广东省有21个市,若要计算该矩阵的最短距离,则需要将字符串改为21进制,即城市编号在个体字符串表现为:0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F、G、H、I、J、K、L。 加多一个函数,将个体字符串,修改回城市编号,然后计算适应度。
若对本文有任何的疑问或建议欢迎评论区留言。