利用遗传算法求解 TSP(旅行商)问题
任务描述
本关任务:利用遗传算法求解 TSP 问题。
相关知识
为了完成本关任务,你需要掌握:1. 遗传算法;2. TSP问题。
遗传算法
一个后继状态由两个父状态决定,以k
个随机产生的状态开始(population
),一个状态表示成一个字符串。
定义一个健康度量函数用来评价状态的好坏程度,通过选择,交叉,突变的操作产生下一轮状态。
TSP问题
旅行商问题,即 TSP 问题(Traveling Salesman Problem)又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。
假设有一个旅行商人要拜访n
个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。
编程要求
在右侧编辑器中补充void select()
和void cross()
函数,解决 TSP 问题,求所有路径中的最小路径路程。
整体流程
initGroup() 基因编码,这里的基因是我们走的城市路线如123456789
popFitness() 适应度计算,这里的适应度指的是选择的城市路线的距离的相反数
chooseBest() 选择适应度最大的基因
void select() 选择复制,这里使用的是轮盘赌的方法
物竞天择、适者生存,针对计算出来的每个个体的适应度,选择合适的复制算子进行有选择的复制。原则就是适应度大的复制的多,适应度小的复制的少,最后保证种群总的数量保持不变。其中复制方法有一下几种:
- 轮盘赌方法
将种群中的所有n个个体适应度累加其值为Si,在[ 0 , S i ]中随机选择n个数,并将n 个个体中第一个个体的适应度比第i 个适应度的值大,则挑选出来,直到挑选出n 个个体为止。- 锦标赛选择法
每次从种群中去除一定数量个体 ,选择其中最好的一个进入子代种群,知道新种群达到原始种群数量。- 随机遍历抽样法
void cross() 染色体交换,从上一步中得到的新种群,一定概率选择其中两个个体进行染色体交换。所谓染色体交换就是,随机选择两个个体,随机选择个体上的n 位进行编码基因的交换。
mutation()个体变异
在完成上一步的染色体交换后的到的种群,以一定概率随机选择其中的某几个个体,对其编码基因进行突变。
#include "stdio.h"
#include "stdlib.h"
#include "time.h"
#define cityNum 10
#define popSize 10
#define croRate 0.85
#define mutRate 0.1
#define MAX 999
//定义染色体的结构
struct Chrom
{
int cityArr[cityNum];
char name;
float adapt;
int dis;
};
struct Chrom genes[popSize];
struct Chrom genesNew[popSize];
struct Chrom temp;
char names[cityNum] = {'A','B','C','D','E','F','G','H','I','J'};
int distance[cityNum][cityNum] = {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
{ 1, 0, 1, 2, 3, 4, 5, 6, 7, 8 },
{ 2, 1, 0, 1, 2, 3, 4, 5, 6, 7 },
{ 3, 2, 1, 0, 1, 2, 3, 4, 5, 6 },
{ 4, 3, 2, 1, 0, 1, 2, 3, 4, 5 },
{ 5, 4, 3, 2, 1, 0, 1, 2, 3, 4 },
{ 6, 5, 4, 3, 2, 1, 0, 1, 2, 3 },
{ 7, 6, 5, 4, 3, 2, 1, 0, 1, 2 },
{ 8, 7, 6, 5, 4, 3, 2, 1, 0, 1 },
{ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }};
void initGroup()
{
int i,j,k;
int t = 0;
int flag = 0;
srand(time(NULL));//产生随机种子
for(i = 0; i < popSize; i ++)
{
temp.name = names[i];
temp.adapt = 0.0f;
temp.dis = 0;
for(j = 0; j < cityNum;)
{
t = rand()%cityNum;
flag = 1;//用于标志之前是否已经访问过该city
for(k = 0; k < j; k ++)
{
if(genes[i].cityArr[k] == t)
{
flag = 0;
break;//表示已经访问过了,重复
}
}
if(flag)//flag没有置0,将该city加入
{
temp.cityArr[j] = t;
genes[i] = temp;
j++;
}
}
}
}
void popFitness()
{
int i,n1,n2;
for(i = 0; i < popSize; i ++)
{
genes[i].dis = 0;
for(int j = 1;j < cityNum; j ++)
{
n1 = genes[i].cityArr[j-1];
n2 = genes[i].cityArr[j];
genes[i].dis += distance[n1][n2];
}
genes[i].dis += distance[genes[i].cityArr[0]][genes[i].cityArr[cityNum-1]];
//循环结束再加上首尾
genes[i].adapt = (float)1/genes[i].dis;
//genes[i]的适应度,路线长度越短该值越大
}
}
int chooseBest()
//选择出适应度最大的基因
{
int choose = 0;
float best = 0.0f;
best = genes[0].adapt;
for(int i = 0; i < popSize; i ++)
{
if(genes[i].adapt < best)
{
best = genes[i].adapt;
choose = i;
}
}
return choose;
}
void select()
/*轮盘赌方法*/
{
float biggestSum = 0.0f;
float adapt_pro[popSize];
float pick = 0.0f;
int i;
for(i = 0; i < popSize; i ++)
{
biggestSum += genes[i].adapt;
}
for(i = 0; i < popSize; i ++)
{
adapt_pro[i] = genes[i].adapt / biggestSum;
}
for(i = 0;i < popSize; i ++)
{
pick = (float)rand()/RAND_MAX;//每次随机生成0~1之间的一个随机数
/********** Begin **********/
float m=0;//m接收累积概率的值
for(int k=0;k<popSize;k++)
{
if(pick<=m+adapt_pro[k])
/*如果随机数 pick≤adapt_pro[1]
genes1被选中
如果随机数 adapt_pro[k−1]<pick≤adapt_pro[k](2≤k≤N)
则genesk被选中*/
{
genesNew[i]=genes[k];
break;
}
m+=adapt_pro[k];//累积概率
}
/********** End **********/
}
for(i = 0;i < popSize; i++)
{
genes[i] = genesNew[i];
}
}
void cross()
{
float pick;
int choice1,choice2;
int pos1,pos2;
int temp;
int conflict1[popSize];
int conflict2[popSize];
int num1;
int num2;
int index1,index2;
int move = 0;
while(move < popSize-1)
{
pick = (float)rand()/RAND_MAX;
if(pick > croRate)
{
move += 2;
continue;
}
choice1 = move;
choice2 = move+1;
pos1 = rand()%popSize;
pos2 = rand()%popSize;
while(pos1 > popSize -2 || pos1 < 1)
{
pos1 = rand()%popSize;
}
while(pos2 > popSize -2 || pos2 < 1)
{
pos2 = rand()%popSize;
}
if(pos1 > pos2)
{
temp = pos1;
pos1 = pos2;
pos2 = temp;
}
for(int j = pos1;j <= pos2; j++)
{
temp = genes[choice1].cityArr[j];
genes[choice1].cityArr[j] = genes[choice2].cityArr[j];
genes[choice2].cityArr[j] = temp;
}
num1 = 0;
num2 = 0;
if(pos1 > 0 && pos2 < popSize - 1)
{
/********** Begin **********/
//如果交叉过程中选择到的部分交叉后会冲突,首先进行记录
/*会冲突的情况
12|3456|789
46|2738|159
此时如果交换则得到12|2738|789,46|3456|159很明显重复了,不是我们需要的,需要先将其位置 记录下来
*/
for(int j=0;j<pos1;j++)
{
for(int k=pos1;k<=pos2;k++)
{
if(genes[choice1].cityArr[j]==genes[choice1].cityArr[k])
conflict1[num1++]=j;
if(genes[choice2].cityArr[j]==genes[choice2].cityArr[k])
conflict2[num2++]=j;
}
}
/********** End **********/
for(int j = pos2 + 1;j < popSize;j++)
{
for(int k = pos1; k <= pos2; k ++)
{
/********** Begin **********/
if(genes[choice1].cityArr[j]==genes[choice1].cityArr[k])
conflict1[num1++]=j;
if(genes[choice2].cityArr[j]==genes[choice2].cityArr[k])
conflict2[num2++]=j;
/********** End **********/
}
}
}
if((num1 == num2) && num1 > 0)
{
/*
这一步的操作就是将记录的自己冲突的值与对方冲突的值进行调换,如上面的例子得到的实际上应该是
14|2738|659
27|3456|189
*/
for(int j = 0;j < num1; j ++)
{
index1 = conflict1[j];
index2 = conflict2[j];
temp = genes[choice1].cityArr[index1];
genes[choice1].cityArr[index1] = genes[choice2].cityArr[index2];
genes[choice2].cityArr[index2] = temp;
}
}
move += 2;
}
}
void mutation()
{
//突变
double pick;
int pos1,pos2,temp;
for(int i = 0;i < popSize; i ++)
{
pick = (float)rand()/RAND_MAX;
if(pick > mutRate)
{
continue;
}
pos1 = rand()%popSize;
pos2 = rand()%popSize;
while(pos1 > popSize - 1)
{
pos1 = rand()%popSize;
}
while(pos2 > popSize - 1)
{
pos2 = rand()%popSize;
}
int a = genes[i].dis;
temp = genes[i].cityArr[pos1];
genes[i].cityArr[pos1] = genes[i].cityArr[pos2];
genes[i].cityArr[pos2] = temp;
popFitness();
if(genes[i].dis > a)
{
temp = genes[i].cityArr[pos1];
genes[i].cityArr[pos1] = genes[i].cityArr[pos2];
genes[i].cityArr[pos2] = temp;
}
}
}