基于遗传算法(Genetic Algorithm)的TSP问题求解(C)
基于遗传算法的TSP问题求解(C)
TSP问题:
TSP(Travelling salesman problem): 译作“旅行商问题”, 一个商人由于业务的需要,要到n个城市,每个城市之间都有一条路径和其他所有的城市相连。现在要求从一个城市出发,穿越所有其他所有的城市,再回到出发的城市。 出于成本的考虑,要求商人走的路径的长短最短。问能否找到这样的一条路径?
这是个经典的NP-complete问题。 时间复杂度为θ(n!)。 随着城市的数量规模增大,在有限的时间内得不到问题的最优解。 我们只能退而求其次,在有限的时间内(或可接受的时间内)找到一个近似最优解,而且这个近似最优解和最优解的误差我们也是可以接受的。 出于这样的考虑,为求解这类问题的启发式,元启发式算法,演化算法营运而生。
随着研究的深入,TSP问题已经演化成好多版本。本文的C程序对于对称和非对称的版本都适用。
遗传算法:
遗传算法(Genetic Algorithm),也称进化算法,是依据生物进化的过程而提出的一种启发式算法,由美国的J.Holland于1975年首次提出。其主要特点是依据生物进化中的“优胜劣汰”或者“竞争”法作为问题的解的选择依据。 直接对结构对象进行操作,不存在求导和函数连续性的限定;具有内在的隐并行性和更好的全局寻优能力;采用概率化的寻优方法,能自动获取和指导优化的搜索空间,自适应地调整搜索方向,不需要确定的规则。遗传算法的这些性质,已被人们广泛地应用于组合优化、机器学习、信号处理、自适应控制和人工生命等领域。它是现代有关智能计算中的关键技术之一
遗传算法的基本原理:
首先根据问题产生多个初始可行解(),然后从初始解中选择诺干优异的个体(问题的解)进行各种操作(交叉,变异)用以产生新的后代。再从新的后代中选择优异的个体进行相应的操作产生它们的后代,如此不断循环,直到迭代的次数达到了预先设定的值或者多次迭代以后产生的最优异的个体(最优解)的质量并没有明显的提高,就可以停止迭代,这时的最优个体(最优解)最为问题的解。
从上面的原理我们可以知晓该算法主要涉及的步骤如图 1所示:
- 编码
在解实现初始化之前,如何表示一个解即编码是一个很重要的问题。 一般的编码方式有:
- 基于二进制编码: 作为遗传算法传统的编码方式。对于TSP问题,经过交叉后可能得不到可行解,需要修补操作。
- 基于矩阵的编码: 需要更多的额外空间,而且随着种群规模的增加,存储空间剧增和前期处理工作任务很庞大。后续操作也比较复杂。
- 基于邻近的编码: 采用链表的方式存储,但是变异,交叉操作复杂
- 基于格雷码方法:传统二进制编码的一种改进,容易实现交叉,变异操作,但是对于该问题不是最优的
- 基于符号编码:对于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)的更多相关文章
- 遗传算法Genetic Algorithm
遗传算法Genetic Algorithm 好家伙,回回都是这个点,再这样下去人估计没了,换个bgm<夜泊秦淮>,要是经典咏流传能投票选诗词,投票选歌,俺一定选这个 开始瞎叨叨 遗传算法的 ...
- 【智能算法】超详细的遗传算法(Genetic Algorithm)解析和TSP求解代码详解
喜欢的话可以扫码关注我们的公众号哦,更多精彩尽在微信公众号[程序猿声] 文章声明 此文章部分资料和代码整合自网上,来源太多已经无法查明出处,如侵犯您的权利,请联系我删除. 00 目录 遗传算法定义 生 ...
- 遗传算法(Genetic Algorithm)——基于Java实现
一.遗传算法原理介绍 遗传算法(Genetic Algorithm)是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法.遗传算法是从代表问 ...
- 超详细的遗传算法(Genetic Algorithm)解析
https://blog.csdn.net/u010451580/article/details/51178225 https://www.jianshu.com/p/c82f09adee8f 00 ...
- 遗传算法 Genetic Algorithm
2017-12-17 19:12:10 一.Evolutionary Algorithm 进化算法,也被成为是演化算法(evolutionary algorithms,简称EAs),它不是一个具体的算 ...
- MIP启发式算法:遗传算法 (Genetic algorithm)
*本文主要记录和分享学习到的知识,算不上原创 *参考文献见链接 本文主要讲述启发式算法中的遗传算法.遗传算法也是以local search为核心框架,但在表现形式上和hill climbing, ta ...
- 遗传算法(Genetic Algorithm, GA)及MATLAB实现
遗传算法概述: • 遗传算法(Genetic Algorithm,GA)是一种进化算法,其基本原理是仿效生物界中的“物竞天择.适者生存”的演化法则,它最初由美国Michigan大学的J. Hollan ...
- Evolutionary Computing: 3. Genetic Algorithm(2)
承接上一章,接着写Genetic Algorithm. 本章主要写排列表达(permutation representations) 开始先引一个具体的例子来进行表述 Outline 问题描述 排列表 ...
- Evolutionary Computing: 2. Genetic Algorithm(1)
本篇博文讲述基因算法(Genetic Algorithm),基因算法是最著名的进化算法. 内容依然来自博主的听课记录和教授的PPT. Outline 简单基因算法 个体表达 变异 重组 选择重组还是变 ...
随机推荐
- 学而有道--思维导图式总结(一):Nosql分类
前言: 众所周知,学习是需要方法的.作为一名java程序员,我们需要学习无数的技能,然而我们的大脑并不买账,学习了一项知识,时间一久就会遗忘, 如何更好高效的回忆起曾经学习过的知识,是极其重要的. 有 ...
- vue-admin-template模板添加tagsview
参考: https://github.com/PanJiaChen/vue-admin-template/issues/349 一.从vue-element-admin复制文件: vue-admin- ...
- xlwt/xlwt/Style.py excel样式源文件
from __future__ import print_function # -*- coding: windows-1252 -*- from . import Formatting from . ...
- LUOGU P3960 列队 (noip2017 day2T3)
传送门 解题思路 记得当时考试我还是个孩子,啥也不会QAQ.现在回头写,用动态开点的线段树,在每行和最后一列开线段树,然后对于每次询问,把x行y列的删去,然后再把x行m列的元素加入x行这个线段树,然后 ...
- xshell添加脚本
##### xshell添加脚本```属性连接 - 用户身份验证 - 登陆脚本 - 添加等待:[usmshell]$发送:open 212 //212是指188那台机器的ID再添加一个等待:passw ...
- 原生JS制作验证码(优化)
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- IO流19(完) --- RandomAccessFile实现数据的插入 --- 技术搬运工(尚硅谷)
原hello.txt文件中的内容:abcdefghijklmn 想要实现的效果是,将xyz插入到abc后面,将文件内容变成:abcxyzdefghijklmn @Test public void te ...
- Python数据分析入门与实践
Python数据分析入门与实践 整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频的感受,单论单个知识点课程本身没问题,大家看的时候可以关 ...
- c# 将Datarow转成Datarowview
DataRowView rowview= dataTable.DefaultView.Cast<DataRowView>().Where(a => a.Row == tmprow). ...
- 2019-8-31-dotnet-如何调试某个文件是哪个代码创建
title author date CreateTime categories dotnet 如何调试某个文件是哪个代码创建 lindexi 2019-08-31 16:55:58 +0800 201 ...