【问题定义】

1. 巡回旅行商问题

给定一组n个城市和俩俩之间的直达距离,寻找一条闭合的旅程,使得每个城市刚好经过一次且总的旅行距离最短。
TSP问题也称为货郎担问题,是一个古老的问题。最早可以追溯到1759年Euler提出的骑士旅行的问题。1948年,由美国兰德公司推动,TSP成为近代组合优化领域的典型难题。
TSP是一个具有广泛的应用背景和重要理论价值的组合优化问题。 近年来,有很多解决该问题的较为有效的算法不断被推出,例如Hopfield神经网络方法,模拟退火方法以及遗传算法方法等。

2. 遗传算法

基本遗传算法可定义为一个8元组:
(SGA)=(C,E,P0,M,Φ,Г,Ψ,T)(SGA)=(C,E,P0,M,Φ,Г,Ψ,Τ)(SGA)=(C,E,P0,M,Φ,Г,Ψ,T)
CCC ——个体的编码方法,SGA使用固定长度二进制符号串编码方法;
EEE ——个体的适应度评价函数;
P0P0P0——初始群体;
MMM ——群体大小,一般取20—10020—10020—100;
ФФФ——选择算子,SGASGASGA使用比例算子;
ГГГ——交叉算子,SGASGASGA使用单点交叉算子;
ΨΨΨ——变异算子,SGASGASGA使用基本位变异算子;
ТТТ——算法终止条件,一般终止进化代数为100—500100—500100—500;

3. 问题的表示

对于一个实际的待优化问题,首先需要将其表示为适合于遗传算法操作的形式。用遗传算法解决TSP,一个旅程很自然的表示为n个城市的排列,但基于二进制编码的交叉和变异操作不能适用。
路径表示是表示旅程对应的基因编码的最自然,最简单的表示方法。它在编码,解码,存储过程中相对容易理解和实现。例如:旅程(5-1-7-8-9-4-6-2-3)可以直接表示为(5 1 7 8 9 4 6 2 3)
(1)产生初始种群
一是完全随机产生,它适合于对问题的解无任何先验知识的情况。随机性较强,因而也较公正
二是某些先验知识可转变为必须满足的一组要求,然后在满足这些要求的解中在随机地选取样本。这样选择初始种群可使遗传算法更快的达到最优解。种群有一定的目标性和代表性,但取例不如完全随机的广泛,而且先验知识是否可靠也是一个问题
(2)适应度函数
遗传算法在进化搜索中基本不利用外部信息,仅以适应度函数为依据,利用种群中每个个体的适应度值来进行搜索。TSP的目标是路径总长度为最短,路径总长度的倒数就可以为TSP的适应度函数:
选择
一般地说,选择将使适应度较大(优良)个体有较大的存在机会,而适应度较小(低劣)的个体继续存在的机会也较小。简单遗传算法采用赌轮选择机制,令Σfi表示群体的适应度值之总和,fi表示种群中第i个染色体的适应度值,它产生后代的能力正好为其适应度值所占份额fi/Σfi。
(3)交叉
基于路径表示的编码方法,要求一个个体的染色体编码中不允许有重复的基因码,也就是说要满足任意一个城市必须而且只能访问一次的约束。基本遗传算法的交叉操作生成的个体一般不能满足这一约束条件。
部分匹配交叉操作要求随机选取两个交叉点,以便确定一个匹配段,根据两个父个体中两个交叉点之间的中间段给出的映射关系生成两个子个体
例如,对下面两个父个体的表示,随机地选择两个交叉点“/”
P1:(123/4567/89),P2:(452/1876/93)P1:(1 2 3 / 4 5 6 7 / 8 9), P2:(4 5 2 / 1 8 7 6 / 9 3)P1:(123/4567/89),P2:(452/1876/93)
首先,两个交叉点之间的中间段交换,得到:
o1:(xxx/1876/xx),o2:(xxx/4567/xx)o1:(x x x / 1 8 7 6 / x x), o2:(x x x / 4 5 6 7 / x x )o1:(xxx/1876/xx),o2:(xxx/4567/xx)
其中x表示暂时未定义码,得到中间段的映射关系。,有:
1----4,8----5,7----6,6----7
对子个体1,2中的x部分,分别保留从其父个体中继承未选定城市码,得到: o1:(x23/1876/x9),o2:(xx2/4567/93)o1:(x 2 3 / 1 8 7 6 / x 9 ), o2:( x x 2 / 4 5 6 7 / 9 3 )o1:(x23/1876/x9),o2:(xx2/4567/93)
最后,根据中间段的映射关系,对于上面子个体1的第一个x,使用最初父码1,由1----4
交换得到第一个x为4,类似地子个体1的第二个x,使用最初父码8,由8----5交换得到子个体1的第二个x为5。类似地进行操作,最终得到的子个体为:
o1:(423/1876/59),o2:(182/4567/93)o1:( 4 2 3 / 1 8 7 6 / 5 9),o2:( 1 8 2 / 4 5 6 7 / 9 3 )o1:(423/1876/59),o2:(182/4567/93)
(4)变异
遗传算法解决TSP 问题基于二进值编码的变异操作不能适用,不能够由简单的变量的翻转来实现
在TSP问题中个体的编码是一批城市的序列,随机的在这个序列抽取两个城市,然后交换他们的位置。这样就实现了个体编码的变异,算法如下:
产生两个0到1之间的随机实数;
将这两个随机实数转化为0到n(城市数)-1之间的随机整数;
将这两个随机整数指代的城市进行交换;

【实验原理】

  1. 巡回旅行商问题(TSPTSPTSP)
    给定一组nnn个城市和俩俩之间的直达距离,寻找一条闭合的旅程,使得每个城市刚好经过一次且总的旅行距离最短。
  2. 遗传算法
    (1)在论域空间上定义一个适应度函数f(x)f(x)f(x),给定种群规模NNN,交叉率PcPcPc和变异率PmPmPm,代数TTT;
    (2)随机产生UUU中的NNN个染色体s1,s2,…,sNs1,s2,…,sNs1,s2,…,sN,组成初始种群S=s1,s2,…,sNS={s1,s2,…,sN}S=s1,s2,…,sN,置代数计数t=1t=1t=1;
    (3)计算SSS中每个染色体的适应度f()f()f();
    (4) 若终止条件满足,则取SSS中适应度最大的染色体作为所求结果,算法结束;
    (5)按选择概率p(si)p(si)p(si)所决定的选中机会,每次从SSS中随机选中111个染色体并将其复制,共做NNN次,然后将复制得到的NNN染色体组成群体S1S1S1;
    (6)按PcPcPc所决定的参加交叉的染色体数ccc,从S1S1S1中随机确定ccc个染色体,配对进行交叉操作,并用产生的染色体代替原染色体,组成群体S2S2S2;
    (7)按PmPmPm所决定的变异次数mmm,从S2S2S2中随机确定mmm个染色体,分别进行变异操作,并用产生的新染色体代替原染色体,组成群体S3S3S3;
    (8)将群体S3S3S3作为新种群,即用S3S3S3代替SSS,t=t+1t = t +1t=t+1,转(3)。

【实验内容】

用遗传算法求解TSP问题

1. 基本要求

(1)N>=8N>=8N>=8。
(2)随即生成NNN个城市间的连接矩阵。
(3)指定起始城市。
(4) 给出每一代的最优路线和总路线长度。
(5)以代数TTT作为结束条件,T>=50T>=50T>=50。

2. 程序代码

#include<iostream>
#include<cmath>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>
#define ERROR -1
using namespace std;typedef struct City
{int x,y;   // 城市坐标
}City;
City cities[10];
double distance(int a, int b)   // 计算两城市间距离
{return abs((cities[a].x - cities[b].x) ^ 2 + (cities[a].y - cities[b].y) ^ 2);
}int gene_sum = 9; // 染色体上的基因数
class Population;   // 向前引用,种群类
class Chro  // Chromoesome染色体
{public:Chro();  // 构造函数,将适应度初始化为0void RandomInit();  // 随机初始化double fx();        // 适应度函数void display();     // 展示基因序列void copy(Chro s); // 复制void exchange(Chro &s);// 互换void variation();  // 变异friend int find(int gene[], int start, int end, int x);    // 寻找基因x在染色体上的位置friend class Population;
private:int *gene;double fits;  // 适应度
};
Chro::Chro()
{gene = new int[gene_sum];fits = 0;
}
void Chro::RandomInit() // 随机初始化染色体上的基因序列
{int i;time_t t;for (i = 0; i < gene_sum; i++) {gene[i] = i;}srand((int)time(&t));// 确保不会生成重复的随机数for (i = 0; i < 1+int(gene_sum/2); i++) {swap(gene[i], gene[i + rand() % (gene_sum - i)]);}fx();Sleep(1000);
}
double Chro::fx()   //  适应度函数fx
{double f = 0;for (int i = 0; i < gene_sum; i++){f += distance(gene[i], gene[(i + 1) % gene_sum]);}fits = 1.0 / f;// 总距离越大,适应度越小return fits;
}
void Chro::display()    // 展示基因序列和该染色体适应度
{cout << "    [";for (int i = 0; i < gene_sum; i++) {cout << " " << gene[i];}cout << " ],  fits= " << fx()<<", distance= "<<1/fx();
}
void Chro::copy(Chro s) //复制
{for (int i = 0; i < gene_sum; i++){gene[i] = s.gene[i];}fits = fx();   // 重新计算适应度
}
int find(int gene[], int start, int end, int x)// 在gene序列中查找x基因的位置并返回
{for (int i = start; i <= end; i++) {if (gene[i] == x) {return i;}}return ERROR;
}
void Chro::exchange(Chro &s)    //将当前染色体与另一染色体s进行基因片段交换
{int i, j = 0, k = 0, repeat;int pos = rand() % (gene_sum - 4);  // 随机选择交叉位置(由于要交换3或4个基因,所以交叉位置只能在[1,n-4]内)int num = 3 + rand() % 2;   // 随机选择交叉的基因数,最小为3,最大为4/*cout << endl; 查看发生交换的位置和位数cout << "pos: " << pos << ", num: " << num << endl;*/int *segment1 = new int[gene_sum];   // 用于记录交换后当前染色体上的基因int *segment2 = new int[gene_sum];  // 用于记录交换后另一染色体上的基因for (i = 0; i < gene_sum; i++) {if (i >= pos && i < pos + num) {segment1[i] = s.gene[i];segment2[i] = gene[i];}else {segment1[i] = gene[i];segment2[i] = s.gene[i];}}int *mapping1 = new int[4];  // 当前染色体中间段的映射int *mapping2 = new int[4];  // 另一染色体中间段的映射for (i = 0; i < 4; i++) {mapping1[i] = ERROR;  // 初值全部为-1mapping2[i] = ERROR;}for (i = pos; i < pos + num; i++) {repeat = find(segment1, pos, pos + num - 1, gene[i]);if (repeat == ERROR) {mapping1[j++] = gene[i];    }repeat = find(segment2, pos, pos + num - 1, s.gene[i]);if (repeat == ERROR) {mapping2[k++] = s.gene[i];}}/* 查看映射cout << "map1   " << "map2" << endl;for (k = 0; k < 4; k++) {cout << mapping1[k] << "     " << mapping2[k] << endl;}*/j = k = 0;for (i = pos; i < pos + num; i++) {// 将重复的基因替换为映射中的基因repeat = find(gene, 0, pos - 1, segment1[i]);if (repeat != ERROR) {segment1[repeat] = mapping1[j++];}repeat = find(gene, pos + num, gene_sum - 1, segment1[i]);if (repeat != ERROR) {segment1[repeat] = mapping1[j++];}repeat = find(s.gene, 0, pos - 1, segment2[i]);if (repeat != ERROR) {segment2[repeat] = mapping2[k++];}repeat = find(s.gene, pos + num, gene_sum - 1, segment2[i]);if (repeat != ERROR) {segment2[repeat] = mapping2[k++];}}for (i = 0; i < gene_sum; i++) {gene[i] = segment1[i];  // 交叉后的该染色体s.gene[i] = segment2[i];// 交叉后的另一染色体}delete segment1;delete segment2;delete mapping1;delete mapping2;
}
void Chro::variation()
{int pos = rand() % 8; // 随机选择变异位置int temp = gene[pos];   // 将被选中的基因和后面一位基因交换gene[pos] = gene[pos + 1];gene[pos + 1] = temp;
}Chro solution; // 用于记录有史以来最好的染色体
int best_generation = 0;   // 用于记录最好的染色体所在的代数
int chro_sum = 20; // 种群中染色体数
class Population
{public:Population();    // 构造函数void best_fit();// 查找适应度最高的染色体void select(); // 选择void cross();  // 交叉void variation();// 种群变异void display();    // 显示种群内染色体int generation; // 种群代数
private:Chro *chromoesomes; // 染色体double Σf;    // 种群适应度之和double *P;    // 选择概率
};
Population::Population()
{int i;generation = 1;chromoesomes = new Chro[chro_sum];for ( i = 0; i < chro_sum; i++){chromoesomes[i].RandomInit();}Σf = 0;P = new double[chro_sum];for (i = 0; i < chro_sum; i++) {Σf += chromoesomes[i].fits;}for (i = 0; i < chro_sum; i++) {P[i] = chromoesomes[i].fits / Σf;}
}
void Population::best_fit() // 查找适应度最大的染色体并返回其编号
{int best = 0;for (int i = 1; i < chro_sum; i++){if (chromoesomes[best].fx() < chromoesomes[i].fx()) {best = i;}}if (chromoesomes[best].fx() > solution.fits) {solution.copy(chromoesomes[best]);best_generation = generation;}cout << "  The best fit in generation" << generation << " is: " << endl;cout << "  chromoesomes" << best + 1 << ": ";chromoesomes[best].display();cout << endl;
}
void Population::select()   // 种群选择
{int i, j;int *selected = new int[chro_sum];   // 用于记录被选中的染色体号double r;double *q = new double[chro_sum];  // 用于记录积累概率Chro *cp = new Chro[chro_sum];q[0] = P[0]; // 积累概率cout << endl;cout << "  Accumulation of probabilities" << endl;  // 打印积累概率cout << "  q1= " << q[0] << endl;for (i = 1; i < chro_sum; i++) {q[i] = q[i - 1] + P[i];cout << "  q" << i + 1 << "= " << q[i] << endl;}cout << "\n  Roulette wheel" << endl;// 轮盘赌,产生随机数srand(time(NULL));//设置随机数种子,使每次产生的随机序列不同for (int i = 0; i < chro_sum; i++){r = rand() % (10000) / (double)(10000);cout << "  r" << i + 1 << "= " << r << endl;if (r <= q[0]) {selected[i] = 0;   // 选中第一个染色体}for (j = 0; j < chro_sum - 1; j++){if (q[j] <= r && r <= q[j + 1]) {selected[i] = j + 1;   // 选中第j+1个基因}}cp[i].copy(chromoesomes[i]);}for (i = 0; i < chro_sum; i++){chromoesomes[i].copy(cp[selected[i]]);}delete selected;delete q;delete cp;
}
void Population::cross()    // 种群交叉
{for (int i = 0; i < chro_sum; i += 2) {chromoesomes[i].exchange(chromoesomes[i + 1]);}
}
void Population::variation()    // 种群变异
{int probability = rand() % 100;   // 变异积累为1%if (probability==1){int x = rand() % chro_sum; // 随机选择一个染色体变异cout << "  The chromoesome " << x << " is variated!" << endl;chromoesomes[x].variation();}generation++; // 至此,种群进化一代
}
void Population::display()// 依次输出每一条染色体(每种方案)
{cout << endl;int i;for (i = 0; i < chro_sum; i++) {    cout << "  chromoesomes" << i + 1 << ": ";chromoesomes[i].display();cout << endl;}cout << endl;
}int main()
{cout << "  Input the number of cities:  "; // 输入城市数(基因数)cin >> gene_sum;cout << "  Input the number of chromoesomes in population:  ";// 输入染色体数cin >> chro_sum;cout << "  Input the location of cities:" << endl;// 输入每个城市坐标(x, y)for (int i = 0; i < gene_sum; i++){cout <<"\t"<< i + 1 << ": ";cin >> cities[i].x >> cities[i].y;}Population P0;cout << "\n  Generation=" << P0.generation << endl;P0.display();P0.best_fit();int T; // 进化的代数cout << "  Input the T:  ";cin >> T;for (int i = 0; i < T; i++) {cout << endl << "  After select: ";  // 选择P0.select();P0.display();cout << endl << "  After cross: ";  // 交叉P0.cross();P0.display();cout << endl << "  After variation: ";// 变异P0.variation();cout << "  Generation=" << P0.generation << endl;P0.display();P0.best_fit();system("pause");system("cls");}cout << "  The best solution in history is:" << endl;    // 打印进化过程中最好的染色体,即解决方案cout << "  in generation" << best_generation;    solution.display();cout << endl;system("pause");return 0;
}

3. 程序运行说明

(1)手动输入城市个数,这里设为999;手动输入染色体数,这里设为202020,手动输入999个城市坐标,随后生成202020条不同的染色体。

初始种群中,适应度最高的染色体是第141414条。输入进化的代数T为100100100代

(2)第一代种群进行选择,这里先输出积累概率和轮盘赌的结果,再显示选择后的种群。

(3)种群选择过后,进行染色体交叉(代码中有关于打印映射函数的部分,这里不做观察,将其注释)

(4)种群交叉后,再进行变异(这一轮没有染色体发生变异)。至此,种群已经进化一代。

(5)按下回车,屏幕刷新,第二代种群开始进行选择、交叉、变异,生成第三代种群(这次依然没有发生变异)


(6)这里设定的变异率为111%,在第959595代种群进化到第969696代的过程中发生了变异。

(7)进化完100100100代,算法结束

(8)显示种群进化过程中适应度最高的染色体
据此得知最佳解决方案在第999代种群中,适应度为0.0333330.0333330.033333,路径为1−4−2−7−0−5−3−8−61-4-2-7-0-5-3-8-61−4−2−7−0−5−3−8−6,距离为303030。

【小结或讨论】

通过本次实验,我完成了遗传算法求解旅行商问题,进一步理解了遗传算法的基本流程。实验数据中的适应度较低是由于适应度为距离的倒数,城市坐标位置将一定程度上影响总体适应度的值,但不会影响进化结果。通过时间生成初始种群中染色体上的基因序列,为避免生成相同的染色体,使用了sleep()函数来调节伪随机数,运行到输入完城市坐标的部分后会停止种群中染色体数*1000ms的时间。交叉函数中,从1到(染色体基因数-4)的位置任意选取一个作为交叉开始位置,连续的3或4位发生交换。设置了映射函数避免交叉后基因重复。实验中输入的城市数量为9较小,染色体数量20和进化代数100较大,导致在进化过程中的适应度趋于固定的几个数。

遗传算法求解TSP问题(C++实现)相关推荐

  1. 人工智能导论——遗传算法求解TSP问题实验

    一.实验目的: 熟悉和掌握遗传算法的原理.流程和编码策略,并利用遗传算法求解组合优化问题,理解求解TSP问题的流程并测试主要参数对结果的影响. 二.实验原理: 旅行商问题,即TSP问题(Traveli ...

  2. 【建模算法】基于遗传算法求解TSP问题(matlab求解)

    [建模算法]基于遗传算法求解TSP问题(matlab求解) TSP (traveling salesman problem,旅行商问题)是典型的NP完全问题,即其最坏情况下的时间复杂度随着问题规模的增 ...

  3. 局部搜索、模拟退火和遗传算法求解TSP问题

    模拟退火和遗传算法求解TSP问题 源代码传送门:GITHUB 数据传送门:TSPLIB 文章目录 模拟退火和遗传算法求解TSP问题 摘要 1 导言 1.1 问题重述 1.2 TSP问题选择 1.3 思 ...

  4. 基于遗传算法求解TSP问题(旅游路径规划,Python实现,超详细,可视化,结果分析)

    ps:作者是很用心写的,如果觉得不错,请给作者一点鼓励噢!(点赞收藏评论噢) 基于遗传算法求解TSP问题 摘要 巡回旅行商问题(TSP)是组合优化中的经典问题.常见的TSP问题求解算法例如穷举法.贪心 ...

  5. 【老生谈算法】matlab实现遗传算法求解TSP问题——TSP问题

    遗传算法求解TSP问题MATLAB实现 1.文档下载: 本算法已经整理成文档如下,有需要的朋友可以点击进行下载 序号 文档(点击下载) 本项目文档 [老生谈算法]遗传算法求解TSP问题MATLAB实现 ...

  6. 【运筹优化】GA遗传算法求解TSP问题(Java实现)

    文章目录 代码 Genome基因类 GeneticAlgorithm_TSP遗传算法类 运行结果 代码 Genome基因类 import lombok.Data; import lombok.NoAr ...

  7. 【Matlab】 遗传算法求解TSP问题

    [Matlab] 遗传算法求解TSP问题 文章目录 [Matlab] 遗传算法求解TSP问题 前言 一.问题描述 二.实验设计 1.问题案例 2.读入数据 3.适应度计算 4. 选择子代 5. 结果输 ...

  8. 基于遗传算法求解TSP问题(JAVA)

    一.TSP问题 TSP问题(Travelling Salesman Problem)即旅行商问题,又译为旅行推销员问题.货郎担问题,是数学领域中著名问题之一.假设有一个旅行商人要拜访n个城市,他必须选 ...

  9. 【人工智能导论】遗传算法求解TSP问题(含源码github)

    源程序:Github链接 Symmetric traveling salesman problem (TSP) Given a set of n nodes and distances for eac ...

  10. 遗传算法求解TSP问题(python版)

    简介 改进和实现遗传算法,用以对旅行商问题(TSP问题)进行建模和近似求解,从而深入对启发式算法的理解. 算法流程 遗传算法解决TSP的流程是以下几部分:初始化种群.计算适应度函数.选择.交叉.变异然 ...

最新文章

  1. mysql+实例夯住的监控_Nagios 里面监控MySQL 监控事务夯住(RUNNING)报警通知
  2. html5和前端精要(5)-基于python脚本(2)
  3. 使用网真和WAD热部署Kubernetes
  4. 耳语(Whisper) app数据泄露致9亿个私密自白曝光:该怎么办?
  5. VB讲课笔记11:多重窗体与环境应用
  6. Python 中的注意点_s2
  7. BZOJ4998 星球联盟(LCT+双连通分量+并查集)
  8. windows xp下Apache2.2.11整合Tomcat6.0.20
  9. 【C语言小游戏】走迷宫
  10. cloverconfig机型修改_Clover的Config新手设置教程
  11. docker exec详解
  12. vue项目 情侣空间 纪念日功能模块
  13. java excel 饼图_Java 在 Excel 中创建饼图/环形图
  14. 数据库上机实验三(游标)
  15. loj534. 「LibreOJ Round #6」花团
  16. 【关于Log4j2】
  17. springboot 发送邮件 QQ邮箱 535error
  18. excel图表美化:用散点标记制作不一样的折线图
  19. pgsql 常用命令
  20. STM32F407VET6的OTM8009_800X480屏幕驱动移植

热门文章

  1. python爬取千图网高清图
  2. 使用Volley+OkHttp+Gson加速Android网络开发
  3. PostgreSQL column cryptographic use pgcrypto extension and optional openssl lib
  4. AVOD阅读笔记(二):多模型融合RPN----Aggregate View Obeject Detection network
  5. 无人机起降(2):AcFly飞控模块与M8N GPS模块的接口修改
  6. 没有GPS模块无人机无法解锁解决方法测试及其他无法解锁APM疑难杂症
  7. python3做答题器_现在很火的答题赢钱游戏,让我来简单教你怎么做自动答题器...
  8. 常见的计算机端口及服务
  9. php递归函数及简单实例讲解
  10. gentoo 修改键盘映射