基于遗传算法的TSP问题求解(C)

  TSP问题:

  TSP(Travelling salesman problem): 译作“旅行商问题”, 一个商人由于业务的需要,要到n个城市,每个城市之间都有一条路径和其他所有的城市相连。现在要求从一个城市出发,穿越所有其他所有的城市,再回到出发的城市。 出于成本的考虑,要求商人走的路径的长短最短。问能否找到这样的一条路径?

  这是个经典的NP-complete问题。 时间复杂度为θ(n!)。 随着城市的数量规模增大,在有限的时间内得不到问题的最优解。 我们只能退而求其次,在有限的时间内(或可接受的时间内)找到一个近似最优解,而且这个近似最优解和最优解的误差我们也是可以接受的。 出于这样的考虑,为求解这类问题的启发式,元启发式算法,演化算法营运而生。

  随着研究的深入,TSP问题已经演化成好多版本。本文的C程序对于对称和非对称的版本都适用。

  遗传算法:

  遗传算法(Genetic Algorithm),也称进化算法,是依据生物进化的过程而提出的一种启发式算法,由美国的J.Holland于1975年首次提出。其主要特点是依据生物进化中的“优胜劣汰”或者“竞争”法作为问题的解的选择依据。 直接对结构对象进行操作,不存在求导和函数连续性的限定;具有内在的隐并行性和更好的全局寻优能力;采用概率化的寻优方法,能自动获取和指导优化的搜索空间,自适应地调整搜索方向,不需要确定的规则。遗传算法的这些性质,已被人们广泛地应用于组合优化、机器学习、信号处理、自适应控制和人工生命等领域。它是现代有关智能计算中的关键技术之一

  遗传算法的基本原理:

  首先根据问题产生多个初始可行解(),然后从初始解中选择诺干优异的个体(问题的解)进行各种操作(交叉,变异)用以产生新的后代。再从新的后代中选择优异的个体进行相应的操作产生它们的后代,如此不断循环,直到迭代的次数达到了预先设定的值或者多次迭代以后产生的最优异的个体(最优解)的质量并没有明显的提高,就可以停止迭代,这时的最优个体(最优解)最为问题的解。

  从上面的原理我们可以知晓该算法主要涉及的步骤如图 1所示:

  • 编码  

  在解实现初始化之前,如何表示一个解即编码是一个很重要的问题。 一般的编码方式有:

  1. 基于二进制编码: 作为遗传算法传统的编码方式。对于TSP问题,经过交叉后可能得不到可行解,需要修补操作。
  2. 基于矩阵的编码: 需要更多的额外空间,而且随着种群规模的增加,存储空间剧增和前期处理工作任务很庞大。后续操作也比较复杂。
  3. 基于邻近的编码: 采用链表的方式存储,但是变异,交叉操作复杂
  4. 基于格雷码方法:传统二进制编码的一种改进,容易实现交叉,变异操作,但是对于该问题不是最优的
  5. 基于符号编码:对于TSP问题,我们直接使用路径来表示一个染色体。即使用一个整数数组,数组长度为TSP城市的数量,数组中存储各个城市编号,从下标为0开始逐个遍历数组元素形成的一个序列即为路径(对于要回到原点的要求,为了方便表示,在这里不作考虑,只是在计算路径长度的时候会添加相应的长度)。
  • 形成初始解

  采用随机的方式产生诺干个(种群规模)的序列,即产生符合城市编号的随机数存储在数组中,数组中的元素包含所有的城市编号,而且没有重复的编码。数组的个数为种群规模。

  • 选择

  在形成一定数量的可行解(染色体)后,需要对这些父代的染色体进行筛选。根据生物遗传的基因的优胜劣汰原则,在筛选染色体的我们也会秉承精英保留原则,使得优异的基因有更多的机会得到遗传。

  适应度函数: 这里我们选择路径长度的倒数来作为解的适应度

  在这个问题中,我们选择“轮盘赌”算法来筛选染色体。

  基本原理:计算每个染色体(路径)的长度的倒数,再得到所有路径倒数之和,用每条路径的倒数除以所有所有路径倒数之和即为这条路径被选中的概率(路径越短,概率越高)。

  • 交叉

  这里我们选择交替位置交叉法(Alternating Position Crossover,APX)来对一对染色体进行交叉操作,其基本原理如下图所示

  左边为父代的两个染色体,右边为子代染色体。 将左上的数组第一个元素放入右上数组的第一位置中,再转移到左下数组第一个元素,查看右上数组是否已经包含了该元素,如果未包含将其插入右上数组中,否则插入右下数组中。接着从左上数组的第二个元素开始,到左下第二个元素,和前次同意的判断操作。如此类推直到右边两个数组都被填满了为止。

  交叉概率:交叉概率对于解的收敛速度有着重要的影响。一般选择0.6-1。

  • 变异

  生物的进化,除了遗传父母的基因,还有自身基因有一定的概率突变。基于这个原理,变异操作在一定的概率上是作用于染色体自身的。

  变异概率:一定的概率师兄自身基因的改变

  在这个问题中,我们选择位置倒换法,即染色体上随机的产生两个位置上数值互换。

  • 终止条件

  一般有两种方式停止交叉,变异的操作。一,预先设定迭代次数。二,多次跌代后,解的质量得不到一定要求的提高,或者解达到要求的质量,这时都可以停止迭代。这个问题上我们选择第一种。

  

  基于TSP问题的遗传算法代码如下:

  

 #include <stdio.h>
#include <tchar.h>
#include <math.h>
#include <stdlib.h>
#include <time.h> int scale; /* 种群规模 */
int cityNum; /* 城市数量 */
int *pathlength; /* 存储种群每个个体路径长度 */
double *cumPropa; /* 存储个体累积概率 */
int bestlength; /* 最佳路径长度 */
int *bestpath; /* 最佳路径 */
float pc; /* 交叉概率 */
float pm; /* 变异概率 */
int count; /* 变异次数 */
int MAX_Gene; /* 迭代次数 */ /* 函数声明 */
void APCrossover(int **,int ,int ,int );
void copy(int *,int **,int ,int );
void cumDistance(int **,int **, int *,int ,int );
void cumulatePro(int **,double *,int *,int );
void copytoBestPath(int *,int **,int ,int );
void copy(int *,int **,int ,int );
int *creatArray1(int );
double *creatArraydoub(int );
int **creatArray2(int , int );
void CrossAndMutation(int **,int );
void cumDistance(int **,int **, int *,int ,int);
void mutation(int **,int);
void Initialize(int **, int , int );
void readfile(int **, int , int );
void rouletteAlgo(int **,double *, int **,int );
void NcopyO(int **,int **); //创建一个整形的二维整数数组
int **creatArray2(int scale, int cityNum)
{
int i; int **ptemp; ptemp=(int **)malloc(sizeof(int *)*scale);
for(i=;i<scale;i++)
ptemp[i]=(int *)malloc(sizeof(int)*cityNum); return ptemp;
}
//创建一个整形的一维数组
int *creatArray1(int scale)
{
int *tempcreate;
tempcreate=(int *)malloc(sizeof(int)*scale);
return tempcreate;
}
//创建一个双精度型的一维数组
double *creatArraydoub(int scale)
{
double *tempcreate;
tempcreate=(double *)malloc(sizeof(double)*scale);
return tempcreate;
} // 二维数组拷贝
void NcopyO(int **newgeneration,int **oldgeneratoin)
{
int i,j;
for(i=;i<scale;i++)
for(j=;j<cityNum;j++)
oldgeneratoin[i][j]=newgeneration[i][j]; } //计算一次迭代各可行解的路径长度
void cumDistance(int **oldgeneration,int **cityDistance, int *pathlength,int cityNum,int scale)
{
int i,j,k;
int length=;
int min=;
int minp; for(i=;i<scale;i++)
{
length=;
for(k=;k<cityNum-;k++)
{
j=k+;
length+=cityDistance[oldgeneration[i][k]][oldgeneration[i][j]];
}
//题目要求回到原点,所以加上回到原点的距离
pathlength[i]=length+cityDistance[oldgeneration[i][cityNum-]][oldgeneration[i][]]; if(pathlength[i]<min)
{
min=pathlength[i];
minp=i;
}
}
if(min<bestlength)
{
bestlength=min;
//将每代最优解直接加入下一代中,即“精英保留”原则
copytoBestPath(bestpath,oldgeneration,minp,cityNum);
} } void copytoBestPath(int *bestpath,int **oldGeneration,int position,int cityNum)
{
int i;
for(i=;i<cityNum;i++)
bestpath[i]=oldGeneration[position][i];
} //计算一次迭代中各个可行解作为交叉操作的累积概率 void cumulatePro(int **generation,double *cumPropa,int *pathlength,int scale)
{
int i;
double sumlength=; for(i=;i<scale;i++)
sumlength+=/(double)pathlength[i]; cumPropa[]=(/(double)pathlength[])/sumlength; for(i=;i<scale;i++)
{
cumPropa[i]=(/(double)pathlength[i])/sumlength+cumPropa[i-];
}
} //初始化话生产相应规模的可行解
void Initialize(int **geneSolution, int scale, int cityNum)
{
int i,j,k;
int randnum;
bool exist; srand(time(NULL)); for(i=;i<scale;i++)
for(j=;j<cityNum;j++)
{
exist=true;
while(exist==true){
//注意:rand()/Rand_MAX 结果只能是0, 应该先进行类型转换
randnum=(int)(((double)rand()/RAND_MAX)*cityNum);
for(k=;k<j;k++)
if(geneSolution[i][k]==randnum)
break;
if(k==j)
exist=false;
}
geneSolution[i][j]=randnum;
} } //读取文件中的各相邻点的距离信息
void readfile(int **cityDistance, int scale, int cityNum)
{
int i,j;
FILE *fp;
errno_t err; err=fopen_s(&fp,"data.txt","r"); if(err!=)
{
printf("The file can not be found!\n");
}
else
{ for(i=;i<scale;i++)
{
for(j=;j<cityNum;j++)
{
fscanf_s(fp,"%d ",&cityDistance[i][j]);
}
} fclose(fp);
} } //拷贝一条路径
void copy(int *oldGeneration,int **newGeneration,int position,int cityNum)
{
int i;
for(i=;i<cityNum;i++)
newGeneration[position][i]=oldGeneration[i];
} //使用轮盘赌算法选择交叉的对象
void rouletteAlgo(int **oldGeneration,double *cumPropa, int **newGeneration,int scale)
{
int i,j;
double randNum; for(i=;i<scale-;i++)
{
randNum=(double)rand()/RAND_MAX;
if(cumPropa[]>=randNum)
copy(oldGeneration[],newGeneration,i,scale);
else
{
for(j=;j<scale;j++)
if(randNum>cumPropa[j] && randNum<=cumPropa[j])
break;
copy(oldGeneration[i],newGeneration,i,scale);
}
} copy(bestpath,newGeneration,scale-,scale); } //交叉操作:交替位置交叉法(Alternating Position Crossover,APX)
void APCrossover(int **newgeneration,int p1,int p2,int cityNum)
{
int i;
int s1=;
int s2=; int *tempArray1;
int *tempArray2; tempArray1=creatArray1(cityNum);
tempArray2=creatArray1(cityNum); for(i=;i<cityNum;i++)
{
tempArray1[i]=newgeneration[p1][i];
tempArray2[i]=newgeneration[p2][i];
} int m,n;
m=;
n=; while(s1< || s2<)
{
for(i=;i<s1;i++)
if(tempArray1[m]==newgeneration[p1][i])
break;
if(i==s1)
{
newgeneration[p1][s1]=tempArray1[m];
m++;
s1++;
}
else{
newgeneration[p2][s2]=tempArray1[m];
m++;
s2++;
} for(i=;i<s1;i++)
if(tempArray2[n]==newgeneration[p2][i])
break;
if(i==s1)
{
newgeneration[p1][s1]=tempArray2[n];
n++;
s1++;
}
else{
newgeneration[p2][s2]=tempArray2[n];
n++;
s2++;
}
} } //变异操作
void mutation(int **newgeneration,int p1)
{
int rand1,rand2;
int temp;
int i; srand(time(NULL)); for(i=;i<count;i++)
{ rand1=(int)(((double)rand()/RAND_MAX)*cityNum);
rand2=(int)(((double)rand()/RAND_MAX)*cityNum);
while(rand1==rand2)
{
rand2=(int)(((double)rand()/RAND_MAX)*cityNum);
} temp=newgeneration[p1][rand1];
newgeneration[p1][rand1]=newgeneration[p1][rand2];
newgeneration[p1][rand2]=temp;
} } //对一代群体进行交叉变异操作
void CrossAndMutation(int **newgeneration,int cityNum)
{
float rand1,rand2;
int k;
for(k=;k<cityNum;k=k+)
{
srand(time(NULL));
rand1=(float)rand()/RAND_MAX; if(rand1>pc)
{
APCrossover(newgeneration,k,k+,cityNum);
}
else
{
rand2=(float)rand()/RAND_MAX;
if(rand2>pm)
{
mutation(newgeneration,k);
}
rand2=(float)rand()/RAND_MAX;
if(rand2>pm)
{
mutation(newgeneration,k+);
} }
} } int main()
{
int **a;
int **oldGeneration;
int **newGeneration;
int i,j; MAX_Gene=;
cityNum=;
scale=;
bestlength=;
pc=0.6;
pm=0.5;
count=;
int m; a=creatArray2(scale,cityNum);
pathlength=creatArray1(scale);
cumPropa=creatArraydoub(scale);
bestpath=creatArray1(cityNum); readfile(a,scale,cityNum); oldGeneration=creatArray2(scale,cityNum);
newGeneration=creatArray2(scale,cityNum); Initialize(newGeneration,scale,cityNum); for(m=;m<MAX_Gene;m++)
{
NcopyO(newGeneration,oldGeneration);
cumDistance(oldGeneration,a,pathlength,scale,cityNum);
cumulatePro(oldGeneration,cumPropa,pathlength,scale);
rouletteAlgo(oldGeneration,cumPropa,newGeneration,scale);
CrossAndMutation(newGeneration,cityNum); } printf("The best path is :\n"); for(i=;i<cityNum;i++)
{
printf("%d ",bestpath[i]);
}
printf("\n"); printf("The minmum length is %d\n",bestlength); return ;
}

  遗传算法只能得到问题的近似最优解,而且对不同的问题,该算法的性能也不一样。一般要求结合问题的一些特点和属性或者与其他的演化算法相结合,例如蚁群算法,粒子群算法,模拟退火法等。

  

  

  

  

  

  

基于遗传算法(Genetic Algorithm)的TSP问题求解(C)的更多相关文章

  1. 遗传算法Genetic Algorithm

    遗传算法Genetic Algorithm 好家伙,回回都是这个点,再这样下去人估计没了,换个bgm<夜泊秦淮>,要是经典咏流传能投票选诗词,投票选歌,俺一定选这个 开始瞎叨叨 遗传算法的 ...

  2. 【智能算法】超详细的遗传算法(Genetic Algorithm)解析和TSP求解代码详解

    喜欢的话可以扫码关注我们的公众号哦,更多精彩尽在微信公众号[程序猿声] 文章声明 此文章部分资料和代码整合自网上,来源太多已经无法查明出处,如侵犯您的权利,请联系我删除. 00 目录 遗传算法定义 生 ...

  3. 遗传算法(Genetic Algorithm)——基于Java实现

    一.遗传算法原理介绍 遗传算法(Genetic Algorithm)是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法.遗传算法是从代表问 ...

  4. 超详细的遗传算法(Genetic Algorithm)解析

    https://blog.csdn.net/u010451580/article/details/51178225 https://www.jianshu.com/p/c82f09adee8f 00 ...

  5. 遗传算法 Genetic Algorithm

    2017-12-17 19:12:10 一.Evolutionary Algorithm 进化算法,也被成为是演化算法(evolutionary algorithms,简称EAs),它不是一个具体的算 ...

  6. MIP启发式算法:遗传算法 (Genetic algorithm)

    *本文主要记录和分享学习到的知识,算不上原创 *参考文献见链接 本文主要讲述启发式算法中的遗传算法.遗传算法也是以local search为核心框架,但在表现形式上和hill climbing, ta ...

  7. 遗传算法(Genetic Algorithm, GA)及MATLAB实现

    遗传算法概述: • 遗传算法(Genetic Algorithm,GA)是一种进化算法,其基本原理是仿效生物界中的“物竞天择.适者生存”的演化法则,它最初由美国Michigan大学的J. Hollan ...

  8. Evolutionary Computing: 3. Genetic Algorithm(2)

    承接上一章,接着写Genetic Algorithm. 本章主要写排列表达(permutation representations) 开始先引一个具体的例子来进行表述 Outline 问题描述 排列表 ...

  9. Evolutionary Computing: 2. Genetic Algorithm(1)

    本篇博文讲述基因算法(Genetic Algorithm),基因算法是最著名的进化算法. 内容依然来自博主的听课记录和教授的PPT. Outline 简单基因算法 个体表达 变异 重组 选择重组还是变 ...

随机推荐

  1. BASE64Encoder及BASE64Decoder查看源代码方法

    一直以来Base64的加密解密都是使用sun.misc包下的BASE64Encoder及BASE64Decoder的sun.misc.BASE64Encoder/BASE64Decoder类.这人个类 ...

  2. php 7.2下mcrypt扩展支持附带的问题

    按照网上提供的mcrypt扩展编译支持方法,完成了扩展编译,也确实可以正常加密/解密了 但是如果php.ini中配置为: error_reporting = E_ALL display_errors ...

  3. 跟我一起安装vmware

    第一步查看我们的电脑配置 我是windows10,下面的方法是windows10来安装vmware 第二步双击下图文件 (1) 2)弹出如下图,点击下一步即可. (3)一直点击下一步(期间会同意,勾选 ...

  4. 04.Hibernate常用的接口和类---SessionFactory类和作用

    是一个生成Session的工厂类 特点: 1.由Configuration通过加载配置文件创建该对象. SessionFactory factory = config.buildSessionFact ...

  5. NOIP2018提高组初赛选讲

    说实话,这次的初赛比上一次的要简单. 不过还有些变态的题目. 在一条长度为1 的线段上随机取两个点,则以这两个点为端点的线段的期望 长度是( ). A. 1 / 2 B. 1 / 3 C. 2 / 3 ...

  6. mysql基础教程(三)-----增删改、子查询、创建管理表、约束和分页

    插入 INSERT语句语法 从其它表中拷贝数据 • 不必书写 VALUES 子句. • 子查询中的值列表应与 INSERT 子句中的列名对应 update语句 • 可以一次更新多条数据. • 如果需要 ...

  7. 软件-MQ-MQ:IBM MQ

    ylbtech-软件-MQ-MQ:MQ(IBM MQ) MQ传递主干,在世界屡获殊荣. 它帮您搭建企业服务总线(ESB)的基础传输层.IBM WebSphere MQ为SOA提供可靠的消息传递.它为经 ...

  8. 《DSP using MATLAB》Problem 7.34

    代码: %% ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ %% Output In ...

  9. 两天了。照着SVN的界面画的一个界面。

      可以选择显示哪些列. 界面上的东西,都简单,麻烦的是它的下层.下层全部用svn server的服务器自带的svn.exe来支持. 有些位置要启动svn.exe不止一次.所以参数的来回传递,来回组合 ...

  10. 2016年省赛 G Triple Nim

    2016年省赛 G Triple Nimnim游戏,要求开始局面为先手必败,也就是异或和为0.如果n为奇数,二进制下最后一位只有两种可能1,1,1和1,0,0,显然异或和为1,所以方案数为0如果n为偶 ...