利用遗传算法解决TSP问题(C++)
本次文章的初衷
前段时间完成C++大作业,老师要求大家使用遗传算法解决TSP问题并将结果输出出来。在到处查阅资料之后发现那些破玩意也跑不了啊。于是在这里分享给大家利用C++解决该问题的源代码。(因为交作业的时候用的是英文注释,我也就不翻译了,各位看官需要的话自己翻译一下)。
遗传算法的分析
在贴代码之前请先容我瞎bb几句关于这个…所谓的遗传算法
可能听起来十分高大上但是实际上并不是很难
首先呢,需要从生物方面的知识来理解一下,相信大家在高中一定都学过基因的相关知识。那么,一个种类的生物在上千年甚至于说万年的过程中是如何进行不断地进化的呢?从简单了说是自然界的不断择优,优胜劣汰。但是究其本质还是子代生成的过程当中染色体发生交叉互换、变异等等所导致的。那么我们在这里附上一个生物种群进化的流程图:
同理,从这张图中我们可以得到这样的一个想法。一个问题的解决方法是否也可以化做一个序列,按照这种进化的方式不断地去“交叉互换”、“发生突变”是不是也可以得到一个极限接近最优的解呢?
答案是肯定的😜
在TSP问题中,我们要解决的是一系列点按照一种特定的顺序走,路径的总长度最短的问题。那么假如说我们要走北京、天津、沈阳、大连、武汉、长沙这样的一个一个城市集的话,就可以为每一个城市进行编号。即1到6(也可以说0-5,随您的意)。那么每一次走的方案就可以看作一个染色体上的基因序列。那么我们先随机生成x个序列作为第一代,然后每一次生成下一代路径的时候将路径进行片段的互换并且进行个体的突变,在足够多的迭代次数下就可以得到近似最优解。当然啦,为了模拟自然选择的过程,在我的代码中使用了轮盘赌的方法来进行解决:即越“好”的路径可以得到更大的概率成为最终的下一代,具体实现方法还请大家看代码。
话不多说,下面我们看代码
代码部分
为了充分使用C++面对对象编程的特点,咳咳咳
(面对过程也很好啊,可惜老师非要用类,这咋办呢)
我们使用类的方法来解决这个问题
其中我们使用Site来表示学校中的地点(即TSP问题中的城市位置)
使用Chromosome表示染色体(即路径、也就是解)
使用Group表示整个种群(即所有解的集合)
头文件代码如下
//Site.h
#pragma once
class Site
{
public:
Site();
int x;
int y;
};
//Chromosome.h
#pragma once
#include "Site.h"
#include <random>
using namespace std;
class Chromosome
{
public:
Chromosome();
void RandomInit();
double fx();
void display();
void copy(Chromosome s);
void exchange(Chromosome& s);
void variation();
friend int find(int gene[], int start, int end, int x);
double distance(int a, int b);
friend class Population;
double fits;
int* get_gene();
private:
int* gene;
Site sites[8];
};
//Group.h
#pragma once
#include "Chromosome.h"
class Group
{
public:
Group();
void best_fit();
void select();
void cross();
void variation();
void display();
int generation;
friend class Chromosome;
Chromosome solution;
int best_generation;
private:
Chromosome* chromoesomes;
double Σf;
double* P;
};
然后下面是源文件代码(cpp)
//main.cpp
#include "Site.h"
#include "Chromosome.h"
#include "Group.h"
#include <fstream>
#include <iostream>
#include <graphics.h>
void print_route(int* route,Group g);
int main() {
//start the algorithm
//init the group
Group g;
cout << "Generation = " << g.generation << endl;
g.display();
g.best_fit();
//go on the generate
for (int i = 0; i < 500; i++) {
g.select();
g.cross();
g.variation();
g.best_fit();
}
//get the best rank
int* route = g.solution.get_gene();
print_route(route,g);
return 0;
}
void print_route(int* route, Group g) { //print the result
int sites[8][2];
int after_rank[8][2];
ifstream in;
in.open("data.txt");
for (int i = 0; i < 8; i++) {
in >> sites[i][0];
in >> sites[i][1];
}
in.close();
for (int i = 0; i < 8; i++) { //now we make the after_rank array
after_rank[i][0] = sites[route[i]][0];
after_rank[i][1] = sites[route[i]][1];
}
ofstream out; //now we write it into the file
out.open("E:\\output.txt");
out << "Route:" << endl;
for (int i = 0; i < 8; i++) {
out << "[" << after_rank[i][0] << "," << after_rank[i][1] << "]" << endl;
}
out << endl << "Generation:\t" << g.best_generation << endl;
out << "Length:\t" << 1 / g.solution.fits;
out.close();
initgraph(640, 424); //print the graph
IMAGE background;
loadimage(&background, _T("E:\\bjtu.jpg"));
putimage(0, 0, &background);
setlinecolor(BLACK);
for (int i = 0; i < 8; i++) {
line(after_rank[i][0], after_rank[i][1],
after_rank[(i + 1) % 8][0], after_rank[(i + 1) % 8][1]);
}
system("pause");
closegraph();
}
//Site.cpp
#include "Site.h"
Site::Site() {
//initialize the value as 0
this->x = 0;
this->y = 0;
}
//Chromosome.cpp
#include "Chromosome.h"
#include <iostream>
#include <fstream>
using namespace std;
Chromosome::Chromosome()
{
gene = new int[8];
fits = 0;
ifstream file;
file.open("data.txt");
for (int i = 0; i < 8; i++) {
file >> sites[i].x;
file >> sites[i].y;
}
file.close();//close the file
}
void Chromosome::RandomInit() // randomly init the gene
{
static default_random_engine dre;
static uniform_int_distribution<int>u(0, 100000);
int i;
for (i = 0; i < 8; i++) {
gene[i] = i;
}
for (i = 0; i < 1 + int(8 / 2); i++) {
int temp = u(dre);
swap(gene[i], gene[i + temp % (8 - i)]); //swap the gene
}
fx();
}
double Chromosome::fx() // calculate the fits
{
double f = 0;
for (int i = 0; i < 7; i++) {
f += distance(gene[i], gene[(i + 1) % 8]);
}
fits = 1.0 / f;
return fits;
}
void Chromosome::display() // show the information
{
cout << " [";
for (int i = 0; i < 8; i++) {
cout << " " << gene[i];
}
cout << " ], fits= " << fx() << ", distance= " << 1 / fx();
}
void Chromosome::copy(Chromosome s)
{
for (int i = 0; i < 8; i++) {
gene[i] = s.gene[i];
}
fits = fx(); // calculate the fitness again
}
int find(int gene[], int start, int end, int x)// find the position of x and return it
{
for (int i = start; i <= end; i++) {
if (gene[i] == x) {
return i;
}
}
return -1; //mark
}
void Chromosome::exchange(Chromosome& s) //Gene exchange
{
static default_random_engine dre;
static uniform_int_distribution<int>u(0, 100000);
int i, j = 0, k = 0, repeat;
int pos = u(dre) % (8 - 4); // Random selection of crossover position
int num = 3 + u(dre) % 2; // Randomly select the number of genes to cross, the minimum is 3, the maximum is 4
int* segment1 = new int[8]; // Record the genes on the current chromosome after the exchange of records
int* segment2 = new int[8]; // Record the genes on the current chromosome before the exchange of records
for (i = 0; i < 8; 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]; // Mapping of the current chromosome exchange segment
int* mapping2 = new int[4]; // Mapping of another chromosome swap segment
for (i = 0; i < 4; i++) {
mapping1[i] = -1; // initialize the mapping
mapping2[i] = -1;
}
for (i = pos; i < pos + num; i++) {
repeat = find(segment1, pos, pos + num - 1, gene[i]);
if (repeat == -1) {
mapping1[j++] = gene[i];
}
repeat = find(segment2, pos, pos + num - 1, s.gene[i]);
if (repeat == -1) {
mapping2[k++] = s.gene[i];
}
}
j = k = 0;
for (i = pos; i < pos + num; i++) {// replace
//If the gene that was originally outside is found inside
//, the mapping value is assigned to the outside gene
repeat = find(gene, 0, pos - 1, segment1[i]);
if (repeat != -1) {
segment1[repeat] = mapping1[j++];
}
repeat = find(gene, pos + num, 8 - 1, segment1[i]);
if (repeat != -1) {
segment1[repeat] = mapping1[j++];
}
repeat = find(s.gene, 0, pos - 1, segment2[i]);
if (repeat != -1) {
segment2[repeat] = mapping2[k++];
}
repeat = find(s.gene, pos + num, 8 - 1, segment2[i]);
if (repeat != -1) {
segment2[repeat] = mapping2[k++];
}
}
for (i = 0; i < 8; i++) {
gene[i] = segment1[i]; // Chromosome after crossover
s.gene[i] = segment2[i];// another
}
delete[]segment1;
delete[]segment2;
delete[]mapping1;
delete[]mapping2;
}
void Chromosome::variation()
{
static default_random_engine dre(time(0));
static uniform_int_distribution<int>u(0, 100000);
int pos = u(dre) % 8; // choose the variation position randomly
int temp = gene[pos]; // exchange with the gene behind it
gene[pos] = gene[(pos + 1)%8];
gene[(pos + 1) % 8] = temp;
}
double Chromosome::distance(int a, int b) // calculate the tangle distance
{
return (abs(sites[a].x - sites[b].x) + abs(sites[a].y - sites[b].y));
}
int* Chromosome::get_gene() { //get the gene list
return this->gene;
}
//Group.cpp
#include "Group.h"
#include <iostream>
using namespace std;
Group::Group()
{
int i;
best_generation = 0;
generation = 1;
chromoesomes = new Chromosome[100];
for (i = 0; i < 100; i++) {
chromoesomes[i].RandomInit(); //init
}
Σf = 0;
P = new double[100];
for (i = 0; i < 100; i++) {
Σf += chromoesomes[i].fits;
}
for (i = 0; i < 100; i++) {
P[i] = chromoesomes[i].fits / Σf;
}
}
void Group::best_fit() // find the best one
{
int best = 0;
for (int i = 1; i < 100; i++) {
if (chromoesomes[best].fx() < chromoesomes[i].fx()) {
best = i;
}
}
if (chromoesomes[best].fx() > solution.fits) {
solution.copy(chromoesomes[best]); //copy as the best solution
best_generation = generation;
}
cout << " The best fit in generation" << generation << " is: " << endl;
cout << " chromoesomes" << best + 1 << ": ";
chromoesomes[best].display();
cout << endl;
}
void Group::select()
{
static default_random_engine dre(time(0));
static uniform_int_distribution<int>u(0, 100000);
int i, j;
int* selected = new int[100]; // record the chromosomes
double r;
double* q = new double[100]; // record the Cumulative probability
Chromosome* cp = new Chromosome[100];
q[0] = P[0]; // Cumulative probability
for (i = 1; i < 100; i++) {
q[i] = q[i - 1] + P[i];
}
cout << "\n Roulette wheel" << endl;// generate the random number
for (int i = 0; i < 100; i++) {
r = u(dre) % (10000) / (double)(10000);
if (r <= q[0]) {
selected[i] = 0; // choose the best chromosome
}
for (j = 0; j < 100 - 1; j++) {
if (q[j] <= r && r <= q[j + 1]) {
selected[i] = j + 1; // choose the j+1 one
}
}
cp[i].copy(chromoesomes[i]); //make the better into the group
}
for (i = 0; i < 100; i++) {
chromoesomes[i].copy(cp[selected[i]]);
}
delete selected;
delete q;
delete cp;
}
void Group::cross() // group cross
{
for (int i = 0; i < 100; i += 2) {
chromoesomes[i].exchange(chromoesomes[i + 1]); //Mate
}
}
void Group::variation() // Population variation
{
static default_random_engine dre(time(0));
static uniform_int_distribution<int>u(0, 100000);
int probability = u(dre) % 100; // Variation accumulation is 1%
if (probability == 1) {
int x = rand() % 100; // Randomly select a chromosome variant
cout << " The chromoesome " << x << " is variated!" << endl;
chromoesomes[x].variation();
}
generation++; // Population evolution generation
}
void Group::display()// Output each chromosome in turn (each scheme)
{
cout << endl;
int i;
for (i = 0; i < 100; i++) {
cout << " chromoesomes" << i + 1 << ": ";
chromoesomes[i].display();
cout << endl;
}
cout << endl;
}
然后下面是读取的坐标点文件
110 290
32 153
256 238
370 238
422 118
440 218
478 264
260 385
哦对差点儿忘了,这个算法中为了方便我是写死的,大家可以把对应的变量例如染色体上的基因个数(即城市个数或者是地点个数)以及种群中染色体的数量改一下以便使用。
结语
代码中有一些地方用的是绝对地址访问的,这个地方我就懒得改了,本代码主要还是供大家进行理解。虽说代码这东西天下是一家,但是还是多自己研究一下比较好😂