遗传算法求解TSP问题的总结

本文属于个人学习总结,如有不足之处,请各位大牛指出,谢谢。

其中涉及的代码,个人均按照了一定的工程编码规范编码,使其具有可读性。在编码过程中参考了部分论文,由于记不清楚论文名称和作者了,不能一一列出,抱歉。

一、遗传算法:Genetic Algorithm

优化求解问题的一种方法,模拟生物进化过程,所以称遗传算法。

遗传算法的内容:

1)、编码
2)、适应度函数
3)、遗传进化

      1.编码

遗传算法不能直接处理问题空间的参数,必须把它们转换成遗传空间的由基因按一定结构组成的染色体或个体。这一转换操作就叫做编码,也可以称作(问题的)表示。
评估编码策略常采用以下3个规范:
a)完备性(completeness):问题空间中的所有点(候选解)都能作为GA空间中的点(染色体)表现。
b)健全性(soundness): GA空间中的染色体能对应所有问题空间中的候选解。
c)非冗余性(nonredundancy):染色体和候选解一一对应。
目前的几种常用的编码技术有二进制编码,字符编码,变成编码等。
而二进制编码是目前遗传算法中最常用的编码方法。即是由二进制{0,1}产生通常的0,1字符串来表示问题空间的候选解。它具有以下特点:
a)简单易行
b)符合最小字符集编码原则
c)便于用模式定理进行分析,因为模式定理就是以基础的
2.适应度函数
进化论中的适应度,是表示某一个体对环境的适应能力,也表示该个体繁殖后代的能力。遗传算法的适应度函数也叫评价函数,是用来判断群体中的个体的优劣程度的指标,它是根据所求问题的目标函数来进行评估的。
遗传算法在搜索进化过程中一般不需要其他外部信息,仅用评估函数来评估个体或解的优劣,并作为以后遗传操作的依据。由于遗传算法中,适应度函数要比较排序并在此基础上计算选择概率,所以适应度函数的值要取正值。由此可见,在不少场合,将目标函数映射成求最大值形式且函数值,非负的适应度函数是必要的。
      3.遗传进化
首先确定个体,种群的概念。
个体:每一个基因编码,就是一个单独的个体,没有个个体拥有各自的适应度。
种群:由多个个体构成,遗传进化都操作在种群上。
遗传进化的过程:
选择过程,交叉过程,变异过程。
选择运算:将选择算子作用于群体。选择的目的是把优化的个体直接遗传到下一代或通过配对交叉产生新的个体再遗传到下一代。选择操作是建立在群体中个体的适应度评估基础上的。
交叉运算:将交叉算子作用于群体。遗传算法中起核心作用的就是交叉算子。
变异运算:将变异算子作用于群体。即是对群体中的个体串的某些基因座上的基因值作变动。

二、TSP问题

旅行商问题,即TSP问题(Travelling Salesman Problem)又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。
TSP问题是一个组合优化问题。该问题可以被证明具有NP计算复杂性。因此,任何能使该问题的求解得以简化的方法,都将受到高度的评价和关注。

三、遗传算法求解TSP问题

编码: 采用最直观简单的,也是在tsp问题中最常用的直接编码,用整型数字代表城市,不重复出现且全部出现一次整型数字构成的串代表个体基因。
适应度函数: 由于一条基因序列是所有城市构成的环状,而评定tsp问题的关键是所得的环绕距离,所以适应度函数单纯取总距离的倒数,满足距离越小,适应度越大。
个体: 每一条所有城市构成的基因编码便是个体,每一个个体有其适应度。
种群: 在问题初始化时,为了让个体尽可能多的取得可能情况,选择了随机生成初始种群,即随机产生多个个体。
(以上涉及参数):种群初始数量(PopSize),城市数量(CityNumber)
源代码:
city.h
#ifndef CITY_H
#define CITY_H
struct City
{
	int x, y, number;
};
#endif //CITY_H
Individual.h
#ifndef INDIVIDUAL_H
#define INDIVIDUAL_H
#include "global.h"
class Individual
{
public:
	Individual();

	void initIndividual();

	void computeFitness();
	bool isContainCity(const int&);
	int geneLocation(const int&) const;
	void localSearch();
	bool operator < (const Individual&);
friend class GA;
private:
	int sequence[CityNumber];
	double fitness;
};
#endif //INDIVIDUAL_H

以上说明: 城市采用笛卡尔坐标系坐标描述,城市间距离直接采用欧几里得距离。
个体的私有变量描述了个体的性质,个体类对GA(遗传进化类)友元开放,方便GA操作。 其public函数设计可通过函数名明白。( 其中大部分为了交叉变异而设计)

选择算子:用于选择出个体,进行下一代繁衍,选择出的个体作为双亲,进行基因交叉。 选择的标准在于所有个体都有可能被选择,但适应度高的个体有更大的几率被选择,故采用轮盘赌的选择方法。选择出来的个体放入交叉池,进行进化操作。
交叉算子: 用于个体的交叉过程,交叉池内的个体,依照概率进行两两交叉,每次交叉产生两个个体,关于基因交叉的方式,采用了PMX的交叉方式和OX的交叉方式。
交叉算子:http://blog.csdn.net/u012750702/article/details/54563515
变异算子: 每次交叉产生的个体,都依照概率进行变异,以产生跳板增大问题的优化范围。这里变异算子采用了随机点互换方法。
      淘汰方式: 自然界存在淘汰机制,我们模拟这种机制,通过淘汰,维持了种群的规模,并且使优秀个体的基因得以保留。这里引入精英的概念。 精英是种群中最优秀的个体,精英的数量(Elite)是一个进化过程中的重要参数,在淘汰过程中,精英一定会保留下来,而其余的个体,依照其适应度进行轮盘赌选择保留。

GA.h
#ifndef GA_H
#define GA_H
#include "global.h"
#include "individual.h"
#include 
#include 
#include 
#include 
using namespace std;
class GA
{
public:
	void initGA();
	void printResult(ofstream& fout);
	void printResult();
	void evolve();
private:
	Individual pop[PopSize * 3];

	void Search(Individual* offspring, const int& offspringNum);
	void PMX(const Individual& parent1, const Individual& parent2, Individual& child1, Individual& child2);
	void OX(const Individual &parent1, const Individual &parent2, Individual &child1, Individual &child2);
	void crossover(int& currentSize, const int* parentNum, Individual* offspring);
	void mutation(Individual* offspring, int& offspringNum);
	void rouletteSelection(int*, const int&, const int& num);
	void selection(Individual* offspring, const int offspringNum); 
};
#endif //GA_H
涉及参数: 交叉概率(corssPM),变异概率(variationPM),精英数量(Elite)
完整的全局变量:
global.h
#ifndef GLOBAL_H
#define GLOBAL_H
const int PopSize = 100;
const int CityNumber = 130;
const int MaxGen = 500;
const int LocalSearchTimes = 300;
const int Elite = 5;
const double variationPM = 0.1;
const double crossPM = 0.8;
extern double Distance[CityNumber][CityNumber];
void initCityDist();
#endif // GLOBAL_H
main函数:(在InitCityDist函数中从文件读取了城市信息)
main.cpp
#include "city.h"
#include "GA.h"
#include "individual.h"
#include "global.h"
#include 
#include 
#include 
using namespace std;
int main()
{
	ofstream fout("1.txt");
	srand((unsigned)time(NULL));
	initCityDist();
	GA ga;
	ga.initGA();
	ga.evolve();
	ga.printResult(fout);
	return 0;
} 

通过以上步骤,遗传算法求解TSP问题的基本步骤完成,但存在一个问题,即通过遗传算法不一定能够找到精确的解。遗传算法本身决定了结果不可能精确,只能逼近,为了提高准确性,避免盲目的寻找最高峰,我们通过个体的局部搜索提高准确性。
局部搜索:在每次产生一个新个体之后,对个体进行局部搜索,即在个体的基因邻域内,实现局部搜索,如果发现更优化的个体,则取而代之。这样做的意义在于,每一次进化过程中的交叉,相当于产生了新的基因,使群体的适应度更离散,而对所有个体进行一次局部搜索,则提高了整体的适应水平,能够更优的找到相对优化的解。
邻域:在此选择了基因的一段随机序列reverse,代表其邻域。 形象化就是基因序列所对应的城市环,对随机一段子序列进行解扣子操作。
局部搜索代码在以上的LocalSearch中。

至此,遗传算法求解TSP问题算是告一段落,但是遗传算法仍有很大很多还需要优化的地方,个人代码不够精良,如有建议请指出,不胜感激!

以下放出所有cpp代码:
city.cpp
#include "city.h"
#include "global.h"
#include 
#include 
#include 
#include 
using namespace std;
double Distance[CityNumber][CityNumber];

void initCityDist()
{

    double x,y,number;
	ifstream cin("input.txt");
    City citysInformation[CityNumber];
    for(int i = 0; i < CityNumber; ++i)
    {
        cin >> number >> x >> y;
        citysInformation[i].x = x;
        citysInformation[i].y = y;
        citysInformation[i].number = number;
    }
    memset(Distance, 0, sizeof(Distance));
    for(int i = 0; i < CityNumber; ++i)
        for(int j = i+1; j < CityNumber; ++j)
        {
           Distance[i][j] = Distance[j][i] =sqrt((citysInformation[i].x - citysInformation[j].x)*(citysInformation[i].x - citysInformation[j].x) + (citysInformation[i].y - citysInformation[j].y)*(citysInformation[i].y - citysInformation[j].y) );
        }
}

Individual.cpp
#include "individual.h"
#include "global.h"
#include 
#include 
#include 
#include 
using namespace std;

Individual::Individual() : fitness(0) {}

void Individual::initIndividual()
{
	for (int i = 0; i < CityNumber; ++i)
		sequence[i] = i;
	random_shuffle(sequence, sequence + CityNumber);
	computeFitness();
}
void Individual::computeFitness()
{
    double sumDis = 0;
    for(int i = 0; i < CityNumber-1; ++i)
    {
        sumDis += Distance[sequence[i]][sequence[i+1]];
    }
    sumDis += Distance[sequence[CityNumber-1]][sequence[0]];
    if(sumDis)
        fitness = 1.0 / sumDis;
}
bool Individual::isContainCity(const int& city)
{
    for(int i = 0; i < CityNumber; ++i)
    {
        if(sequence[i] == city)
            return true;
    }
	return false;
}
int Individual::geneLocation(const int &gene) const
{
	for (int i = 0; i < CityNumber; ++i)
	{
		if (sequence[i] == gene)
			return i;
	}
	cout << "error" << endl;
	return 0;
}
void Individual::localSearch()
{
	//int code[CityNumber];
	Individual afterReverse;
	//memcpy(code, sequence, CityNumber*sizeof(int));
	
	for (int i = 0; i < LocalSearchTimes; ++i)
	{
		memcpy(afterReverse.sequence, sequence, CityNumber*sizeof(int));
		
		int point1 = rand() % CityNumber;
		int point2 = rand() % CityNumber;
		
		if (point1 > point2)
			swap(point1, point2);

		reverse_copy(sequence + point1, sequence + point2 + 1, afterReverse.sequence + point1);
		afterReverse.computeFitness();

		if (afterReverse.fitness > fitness)
		{
			memcpy(sequence, afterReverse.sequence, CityNumber*sizeof(int));
			computeFitness();
		}	
	}
}
bool Individual::operator < (const Individual& cmp)
{
    return fitness > cmp.fitness;
}


GA.cpp
#include "GA.h"
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

void GA::initGA() 
{
	for (int i = 0; i < PopSize; i++)
		pop[i].initIndividual();
}
void GA::printResult(ofstream& fout)
{
    fout << "Distance is:" << 1.0 / pop[0].fitness << endl;
	fout << "The route is:" << endl;
	for (int i = 0; i < CityNumber; ++i)
		fout << pop[0].sequence[i] + 1 << "->";
	
	for (int i = 0; i < PopSize; ++i)
	{
		fout << i << ":" << endl;
		for (int j = 0; j < CityNumber; ++j)
			fout << pop[i].sequence[j] + 1 << " ";
		fout << endl;
	}	
}
void GA::printResult()
{
	cout << "Distance is:" << 1.0 / pop[0].fitness << endl;
	cout << "The route is:" << endl;
	for (int i = 0; i < CityNumber; ++i)
		cout << pop[0].sequence[i] + 1 << "->";

	for (int i = 0; i < PopSize; ++i)
	{
		cout << i << ":" << endl;
		for (int j = 0; j < CityNumber; ++j)
			cout << pop[i].sequence[j] + 1 << " ";
		cout << endl;
	}
}
void GA::rouletteSelection(int* selectedNum, const int& size, const int& num)
{
    double P[3 * PopSize];
    double sumfitness = 0;
    for(int i =0; i < size; i++)
        sumfitness += pop[i].fitness;
    P[0] = pop[0].fitness / sumfitness;
    for(int i = 1; i < size - 1; ++i)
    {
            P[i] = pop[i].fitness / sumfitness + P[i-1];
    }
    P[size - 1] = 1.0;
    for (int j = 0; j < num; ++j)
    {
        double random = rand() % 10000 / 10000.00;
		for (int i = 0; i < size; ++i)
        {
            if(random <= P[i])
            {
                selectedNum[j] = i;
                break;
            }
        }
    }
}
void GA::crossover(int& offspringNum, const int* parentsNum, Individual* offspring)
{
	for (int i = 0; i < PopSize - 2; i += 2)
	{
		if (rand() % 100 / 100.0 < crossPM)
		{
			Individual parent1 = pop[parentsNum[i]];
			Individual parent2 = pop[parentsNum[i + 1]];
			Individual child1, child2;

			PMX(parent1, parent2, child1, child2);
			//OX(parent1, parent2, child1, child2);

			child1.computeFitness();
			child2.computeFitness();
			offspring[offspringNum++] = child1;
			offspring[offspringNum++] = child2;
		}
	}
}
void GA::evolve()
{
    for (int times = 0; times < MaxGen; ++times)
    {
        int offspringNum = 0;
		Individual offspring[PopSize * 3];
        int parentsNum[PopSize];

		rouletteSelection(parentsNum, PopSize, PopSize);
		crossover(offspringNum, parentsNum, offspring);
		mutation(offspring, offspringNum);

		Search(offspring, offspringNum);

		selection(offspring, offspringNum);	

    }
}
void GA::mutation(Individual* offspring, int& offspringNum)
{
	int initial = offspringNum;
	for (int i = 0; i < initial; ++i)
	{
		if (rand() % 100 / 100.0 < variationPM)
		{
			int swapCityId1 = rand() % CityNumber;
			int swapCityId2 = rand() % CityNumber;
			Individual child = offspring[i];

			swap(child.sequence[swapCityId1], child.sequence[swapCityId2]);
			child.computeFitness();
			offspring[offspringNum++] = child;
		}
	}
}
void GA::selection(Individual* offspring, const int offspringNum)
{	
	memcpy(offspring + offspringNum, pop, PopSize*sizeof(Individual));
	partial_sort(offspring, offspring + Elite, offspring + PopSize + offspringNum);

	int selectedNum[PopSize - Elite];
	rouletteSelection(selectedNum, PopSize + offspringNum, PopSize - Elite);

	for (int i = 0; i < Elite; ++i)
		pop[i] = offspring[i];
	for (int i = Elite, j = 0; i < PopSize; ++i, ++j)
		pop[i] = offspring[selectedNum[j]];
}
void GA::Search(Individual* offspring, const int& offspringNum)
{
	for (int i = 0; i < offspringNum; ++i)
	{
		offspring[i].localSearch();
	}
}
void GA::PMX(const Individual & parent1, const Individual & parent2, Individual & child1, Individual & child2)
{
	memset(child1.sequence, -1, sizeof(child1.sequence));
	memset(child2.sequence, -1, sizeof(child2.sequence));
	int point1 = rand() % CityNumber;
	int point2 = rand() % CityNumber;
	if (point1 > point2)
		swap(point1, point2);

	for (int i = point1; i < point2; ++i)
	{
		child1.sequence[i] = parent2.sequence[i];
		child2.sequence[i] = parent1.sequence[i];
	}

	for (int i = 0; i < CityNumber; ++i)
	{
		if (i >= point1 && i < point2)
			continue;
	
		int matching = parent1.sequence[i];
		while (child1.isContainCity(matching))
			matching = parent1.sequence[parent2.geneLocation(matching)];		
		child1.sequence[i] = matching;
		
		matching = parent2.sequence[i];
		while (child2.isContainCity(matching))
			matching = parent2.sequence[parent1.geneLocation(matching)];
		child2.sequence[i] = matching;
	}
}
void GA::OX(const Individual& parent1, const Individual& parent2, Individual& child1, Individual& child2)
{
	memset(child1.sequence, -1, sizeof(child1.sequence));
	memset(child2.sequence, -1, sizeof(child2.sequence));
	int point1 = rand() % CityNumber;
	int point2 = rand() % CityNumber;
	if (point1 > point2)
		swap(point1, point2);

	for (int i = point1; i < point2; ++i)
	{
		child1.sequence[i] = parent2.sequence[i];
		child2.sequence[i] = parent1.sequence[i];
	}
	int k = 0;
	int j = 0;
	for (int i = point2; i < CityNumber; ++i)
	{
		while (child1.isContainCity(parent1.sequence[k])) ++k;
		while (child2.isContainCity(parent2.sequence[j])) ++j;
		child1.sequence[i] = parent1.sequence[k++];
		child2.sequence[i] = parent2.sequence[j++];
	}
	for (int i = 0; i < point1; ++i)
	{
		while (child1.isContainCity(parent1.sequence[k])) ++k;
		while (child2.isContainCity(parent2.sequence[j])) ++j;
		child1.sequence[i] = parent1.sequence[k++];
		child2.sequence[i] = parent2.sequence[j++];
	}	
}		


你可能感兴趣的:(遗传算法)