1、问题描述
设有n个城市和距离矩阵D=[dij],其中dij表示城市i到城市j的距离,i,j=1,2 … n,则问题是要找出遍访每个城市恰好一次的一条回路并使其路径长度为最短。
2、算法设计
遗传算法是从代表问题可能潜在的解集的一个种群开始的,初始种群产生之后,按照适者生存和优胜劣汰的原理,逐代演化产生出越来越好的近似解,在每一代,根据问题域中个体的适应度大小选择个体,并借助遗传算子进行组合交叉和变异,产生出代表新的解集的种群。
遗传算法中有选择算子、交叉算子和变异算子。
- 选择算子
选择算子用于在父代种群中选择进入下一代的个体,有轮盘赌选择等。这里保留父代中适应性最佳的个体进入下一代,其余按照轮盘赌选择进入下一代。
- 交叉算子
交叉算子用于对种群中的个体两两进行交叉,有部分映射交叉、顺序交叉、基于位置交叉等交叉算子。这里采用基于位置交叉算子。算法采用顺序交叉算子。具体步骤如下流程所示:
- 随机生成起始位置start和终止位置end,且保证start<end;
- 父代1和父代2保留区间[start,end]的基因,将其分别传给子代1和子代2;
- 将父代2中的基因按照顺序传给子代1,若子代1中已有某个基因,则跳过不放;
- 将父代1中的基因按照顺序传给子代2,若子代2中已有某个基因,则跳过不放
通过下面图片进一步说明顺序交叉算子的执行步骤:
- 变异算子
变异算子用于对种群中的个体进行突变。算法中多次随机选取两个位置基因进行交换。
3、程序流程
遗传算法的基本运算过程如下:
- 初始化:设置进化代数计数器t=0、设置最大进化代数T、交叉概率、变异概率、随机生成M个个体作为初始种群P
- 个体评价:计算种群P中各个个体的适应度。在TSP问题中,总距离越小适应度就越高,就有更大机会能活到下一代
- 选择运算:将选择算子作用于群体。以个体适应度为基础,选择最优个体直接遗传到下一代并通过交叉算子产生新的子代
- 交叉运算:在交叉概率的控制下,对群体中的个体两两进行交叉
- 变异运算:在变异概率的控制下,对群体中的个体进行变异,即对某一个体的基因进行随机调整
- 经过选择、交叉、变异运算之后得到下一代群体P1。
重复以上1-6,直到遗传代数为T,以进化过程中所得到的具有最大适应度个体作为最优解输出,终止计算。
4、代码实现
代码中主要分为两个类:City、GA。
City类中主要实现生成城市数量以及城市之间距离,都是随机生成。
public class City {
private int city_num;
private double[][] city_dist;
public City() {
this.city_num = (int) (5 + Math.random() * 100 % 100);
System.out.println("城市数量:" + this.city_num);
this.city_dist = new double[this.city_num][this.city_num];
for (int i = 0; i < this.city_num; i++) {
for (int j = 0; j < this.city_num; j++) {
this.city_dist[i][j] = 0;
}
}
// 产生(1,100)的随机数
for (int i = 0; i < this.city_num; i++) {
for (int j = i + 1; j < this.city_num; j++) {
this.city_dist[i][j] = (int) (1 + Math.random() * 100 % 100);
this.city_dist[j][i] = this.city_dist[i][j];
}
}
for (int i = 0; i < this.city_num; i++) {
for (int j = 0; j < this.city_num; j++) {
System.out.print(city_dist[i][j] + " ");
}
System.out.println();
}
}
// 获取两个城市之间地距离
public double getDistance(int i, int j) {
return this.city_dist[i][j];
}
// 获取城市地数量
public int getCity_num() {
return city_num;
}
}
GA中主要就是遗传算法的实现步骤,代码中都有进行注释解释,应该能看懂吧。
public class GA {
private double crossRate; // 交叉概率
private double mutationRate; // 变异概率
private int lifeScala; // 种群的规模
private int generationCount; // 迭代次数
private int betsGeneration; // 最佳结果出现的代数
private double best_dist; // 最短距离
private int[][] oldGroup;
private int[][] newGroup;
private int[] bestPath; // 最佳路径
private double[] fitness; // 种群中各个个体的适应度
private double[] pi; // 个体的繁殖概率
private City city;
public GA(double crossRate, double mutationRate, int lifeScala, int generationCount) {
this.crossRate = crossRate;
this.mutationRate = mutationRate;
this.lifeScala = lifeScala;
this.generationCount = generationCount;
this.city = new City();
this.best_dist = Double.MAX_VALUE;
this.newGroup = new int[this.lifeScala][city.getCity_num()];
this.oldGroup = new int[this.lifeScala][city.getCity_num()];
this.fitness = new double[this.lifeScala];
this.pi = new double[this.lifeScala];
this.bestPath = new int[city.getCity_num()];
}
// 初始化种群
// 每一个个体都有一条染色体(路径),要保证染色体上面的不能重复
public void init() {
for (int i = 0; i < this.lifeScala; i++) {
this.oldGroup[i][0] = (int) (Math.random() * this.city.getCity_num() % this.city.getCity_num());
for (int j = 0; j < city.getCity_num(); ) {
this.oldGroup[i][j] = (int) (Math.random() * this.city.getCity_num() % this.city.getCity_num());
int k = 0;
while (k < j) {
if (oldGroup[i][j] == oldGroup[i][k])
break;
k++;
}
if (j == k)
j++;
}
}
}
// 这里求的适应度值就是路径长度
public void getFitness() {
for (int i = 0; i < this.lifeScala; i++) {
double dist = 0.0;
for (int j = 0; j < this.city.getCity_num() - 1; j++) {
dist += this.city.getDistance(this.oldGroup[i][j], this.oldGroup[i][j + 1]);
}
dist += this.city.getDistance(this.oldGroup[i][this.city.getCity_num() - 1], this.oldGroup[i][0]);
this.fitness[i] = dist;
}
}
// 计算每个个体的繁殖概率
public void getPi() {
double sumFitness = 0.0;
for (int i = 0; i < this.lifeScala; i++)
sumFitness += this.fitness[i];
this.pi[0] = fitness[0] / sumFitness;
for (int i = 1; i < this.lifeScala; i++)
this.pi[i] = this.fitness[i] / sumFitness;
}
// 挑选最优个体,直接保留到子代个体
public void selectBestChild(int iteration) {
double minFitness = this.fitness[0];
int minFitnessId = 0;
for (int i = 1; i < this.lifeScala; i++) {
if (minFitness > this.fitness[i]) {
minFitness = this.fitness[i];
minFitnessId = i;
}
}
System.out.println("第" + iteration + "代出现的解:" + minFitness);
if (minFitness < this.best_dist) {
this.best_dist = minFitness;
this.betsGeneration = iteration;
for (int i = 0; i < city.getCity_num(); i++) {
this.bestPath[i] = this.oldGroup[minFitnessId][i];
}
}
// 将最优个体复制到最新种群中的第一个个体中
createNewPolution(0, minFitnessId);
}
// 挑选子代个体,随机挑选
// 这个也可以说是选择算子
public void selectChild() {
double[] acc = new double[this.lifeScala];
acc[0] = this.pi[0];
for (int i = 1; i < this.lifeScala; i++) {
acc[i] = this.pi[i] + acc[i - 1];
}
for (int i = 1; i < this.lifeScala; i++) {
double random = Math.random();
for (int j = 0; j < this.lifeScala; j++) {
if (random < acc[j]) {
createNewPolution(i, j);
break;
}
}
}
}
public void createNewPolution(int newId, int oldId) {
for (int i = 0; i < city.getCity_num(); i++) {
this.newGroup[newId][i] = this.oldGroup[oldId][i];
}
}
// 随机多次变异
// 变异算子
public void mutation(int n) {
int count = (int) (1 + Math.random() * 100 % 100);
int x, y;
for (int i = 0; i < count; i++) {
x = (int) (Math.random() * this.city.getCity_num() % this.city.getCity_num());
y = (int) (Math.random() * this.city.getCity_num() % this.city.getCity_num());
while (x == y) {
y = (int) (Math.random() * this.city.getCity_num() % this.city.getCity_num());
}
int temp = this.newGroup[n][x];
this.newGroup[n][x] = this.newGroup[n][y];
this.newGroup[n][y] = temp;
}
}
// 还差一个交叉算子
// 交叉函数里面有相对于的映射关系,得自己再想想
// 使用两个种群进行交叉变异
// 下面这个进化函数还没写完,其他的写的差不多了,就差调试了
// 部分交叉不会处理冲突,改为顺序交叉
public void cross(int index1, int index2) {
// 随机生成交叉变异的起止点
int start = (int) (Math.random() * this.city.getCity_num() % this.city.getCity_num());
int end = (int) (Math.random() * this.city.getCity_num() % this.city.getCity_num());
while (start == end) {
end = (int) (Math.random() * this.city.getCity_num() % this.city.getCity_num());
}
if (start > end) {
int temp = start;
start = end;
end = temp;
}
// 将个体的染色体暂存
int[] childA = new int[this.city.getCity_num()];
int[] childB = new int[this.city.getCity_num()];
for (int i = 0; i < this.city.getCity_num(); i++) {
childA[i] = this.newGroup[index1][i];
childB[i] = this.newGroup[index2][i];
}
// 将B中的其他基因按照顺序复制给子代A
createNewChrom(this.newGroup[index2], childA, start, end);
// 将A中的其他基因按照顺序复制给子代B
createNewChrom(this.newGroup[index1], childB, start, end);
for (int i = 0; i < this.city.getCity_num(); i++) {
this.newGroup[index1][i] = childA[i];
this.newGroup[index2][i] = childB[i];
}
}
public void createNewChrom(int[] parent, int[] child, int start, int end) {
for (int i = 0, j = 0; i < this.city.getCity_num() && j < this.city.getCity_num(); ) {
int x = parent[i];
if (j >= start && j < end) {
j++;
continue;
}
if (isExit(child, x, start, end)) {
i++;
continue;
}
child[j] = x;
i++;
j++;
}
}
public Boolean isExit(int[] arr, int key, int start, int end) {
for (int i = start; i < end; i++) {
if (arr[i] == key) {
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
// 开始进化
public void startEvolution(int iteration) {
selectBestChild(iteration);
selectChild();
for (int i = 0; i < this.lifeScala; i += 2) {
double cross_random = Math.random();
if (this.crossRate >= cross_random) {
cross(i, i + 1);
}
double random_mutation_1 = Math.random();
double random_mutation_2 = Math.random();
if (this.mutationRate >= random_mutation_1)
mutation(i);
if (this.mutationRate >= random_mutation_2)
mutation(i + 1);
}
}
public void start() {
// 1.初始化种群
init();
getFitness();
getPi();
// 2.种群进化
for (int i = 0; i < this.generationCount; i++) {
startEvolution(i);
// 将得到新的子代放入old里面
for (int j = 0; j < this.lifeScala; j++) {
for (int k = 0; k < this.city.getCity_num(); k++) {
this.oldGroup[j][k] = this.newGroup[j][k];
}
}
// 计算种群新的适应度值
getFitness();
// 计算种群新的繁殖概率
getPi();
}
}
public static void main(String[] args) {
long begin_time = System.currentTimeMillis();
GA ga = new GA(0.9, 0.8, 100, 1000);
ga.start();
long end_time = System.currentTimeMillis();
System.out.print("最佳路径长度");
System.out.println(ga.best_dist);
System.out.print("最佳路径长度出现代数:");
System.out.println(ga.betsGeneration);
for (int i = 0; i < ga.city.getCity_num(); i++)
System.out.print(ga.bestPath[i] + " ");
System.out.println();
System.out.println("运行时间:" + ((end_time - begin_time) / 1000.0));
double dist = 0.0;
for (int i = 0; i < ga.city.getCity_num() - 1; i++)
dist += ga.city.getDistance(ga.bestPath[i], ga.bestPath[i + 1]);
dist += ga.city.getDistance(ga.bestPath[ga.city.getCity_num() - 1], ga.bestPath[0]);
System.out.println(dist);
}
}
最后还是运行调参了,这个就自己手动去调试吧,过程是无限逼近最优解,最后结果大概率可以得到最优解。如果不是最优解也没事,思想领悟到就行。
代码写的有点辣眼睛,大佬们别喷。