旅行商问题(遗传算法)

一.问题描述

假设有一个旅行商人要拜访n(研究n=10)个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。 

二.遗传算法(GA)概述 

遗传算法(Genetic Algorithm)是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法,它最初由美国Michigan大学J.Holland教授于1975年首先提出来的,并出版了颇有影响的专著《Adaptation in Natural and Artificial Systems》,GA这个名称才逐渐为人所知,J.Holland教授所提出的GA通常为简单遗传算法(SGA)。  

三.问题分析 

TSP问题就是寻找一条最短的遍历n 个城市的最短路径, 即搜索自然数子集W= { 1 ,2 , ⋯, n} ( W的元素表示对n 个城市的编号) 的一个排列  π( W) = { V1 , V2 , ⋯, Vn} , 使len = ∑ d ( Vi , Vi+1) + d ( V1 , Vn)取最小值, 式中的d ( Vi , Vi+1) 表示城市Vi 到城市Vi + 1的距离。

遗传算法是具有“生成+检测”的迭代过程的搜索算法。遗传算法是一种群体型操作,该操作以群体中的所有个体为对象。选择、交叉和变异是遗传算法的3个主要操作算子,它们构成了所谓的遗传操作,使遗传算法具有了其它传统方法所没有的特性。遗传算子包含如下6个基本因素: 

1.参数编码:由于遗传算法不能直接处理解空间的解数据,因此必须通过编码将它们表示成遗传空间的基因型串结构数据。 我们采用整数编码的方式,用0-9来表示10个城市。

2.种群初始化:由于遗传算法的群体型操作需要,所以必须为遗传操作准备一个由若干初始解组成的初始种群。初始种群的每个个体都是通过随机方法产生。 

3. 适应度函数:遗传算法在搜索进化过程中一般不需要其他外部信息,仅用适应度值来评估个体或解的优劣,并作为以后遗传操作的依据。 设一个解遍历初始行走的总距离为D,N=1000(根据具体路径而变化),则适应度fitness=pow(2,N/D)。即总距离越高,适应度越低,总距离越低(解越好),适应度越高。

4.选择操作:选择或复制操作是为了从当前群体中选出优良的个体,使它们有机会作为父代为下一代繁殖子孙。个体适应度越高,其被选择的机会就越多。此处采用与适应度成比例的概率方法进行选择。具体地说,就是首先计算群体中所有个体适应度的总和,再计算每个个体的适应度所占的比例,并以此作为相应的选择概率。采用轮盘赌方法。 

5. 交配操作:交配操作是遗传算法中最主要的遗传操作。常用的交配规则如下:常规交配法、基于次序的交配法、基于位置的交配法、基于部分映射的交配法。这里采用基于部分映射的交配法。例如:

父代路径1:  0 6 8 5 1 3 2 9 7 4 0

父代路径2:  0 9 7 8 5 2 3 6 1 4 0

产生一个随机数,假设a = 4,设定交配4位,上面标红的位置,进行交换并放在首位(0之后),然后再从各自的路径中按顺序填充未出现过的数字。如下:

子代路径1:  0 5 2 3 6 8 1 9 7 4 0

子代路径2:  0 1 3 2 9 7 8 5 6 4 0

6.变异操作:变异操作是按位进行的,即把某一位的内容进行变异。变异操作同样也是随机进行的。一般而言,变异概率都取得较小。一般采取对于一个染色体(即个体)随机选取两个基因进行交换的策略。如下:

父代路径:  0 6 8 5 1 3 2 9 7 4 0

子代路径:  0 6 8 9 1 3 2 5 7 4 0

交换后计算两次的城市距离,如若变异后距离变大,则重新变异。

四.具体实现

首先,建立城市位置结构体和功能结构体:

typedef struct CityPosition{

int x;

int y;

}CityPosition;

typedef struct {

         int colony[POPSIZE][CITY_NUM + 1];  //城市种群

         double fitness[POPSIZE];           // 路径适应值

         double Distance[POPSIZE];         //路径实际长度

         int BestRooting[CITY_NUM + 1];     //最优城市路径序列

         double BestFitness;               //最优路径适应值

         double BestValue;                //最优路径长度

}TSP;

然后,定义实现功能函数:

void CalculatDist() //计算每两个城市之间的距离

void InitColony(TSP &city) //初始化种群

void CalFitness(TSP &city) //计算适应值

void Select(TSP &city) //选择操作

void Cross(TSP &city, double pc) //交叉操作

void Mutation(TSP &city, double pm) //变异操作

void OutPut(TSP &city) //输出函数

最后定义实现主函数:

main()

{

CalculatDist();

         InitColony(city);

         CalFitness(city);

         for (i = 0; i < MaxEpoc; i++)

         {

                   Select(city);//选择

                   Cross(city, pcross);//交叉

                   Mutation(city, pmutation);//变异

                   CalFitness(city);//计算适应值                 

         }

         OutPut(city);//输出

}

五.运行结果

旅行商问题(遗传算法)

六.总结

通过遗传算法对10城市的求解,可以看出,用遗传算法来求解TSP问题是可行的。用遗传算法求解时,增加迭代次数,可以得到更加优良的结果,但是会需要更长的时间,即一个优良的结果往往是以时间为代价的,这种情况要依据具体问题具体分析,平衡两者在问题求解时的比重。另外,对选择算法进行优化,会提高遗传算法的性能,这些都需要在实践中不断积累和广泛涉猎优良算法。最后,遗传算法得到的未必是最优解,我们可以根据需要进行多次求解,从而比较得出符合要求的结果。

七.源代码

 

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "math.h"
#include "time.h"
#define CITY_NUM 10     //城市编号是0-9
#define POPSIZE 20
#define MAXVALUE 100   //路径值
#define N 1000            //需要根据实际求得的路径值修正
int Hash[CITY_NUM + 1];
typedef struct CityPosition
{
	int x;
	int y;
}CityPosition;
CityPosition CityPos[10] = { {1,1},{1,4},{1,6},{1,8},{3,8},{5,1},{8,1},{8,2},{8,4},{8,8} };
double CityDistance[CITY_NUM][CITY_NUM];
typedef struct {
	int colony[POPSIZE][CITY_NUM + 1];  //城市种群
	double fitness[POPSIZE];          // 路径适应值
	double Distance[POPSIZE];         //路径实际长度
	int BestRooting[CITY_NUM + 1];      //最优城市路径序列
	double BestFitness;               //最优路径适应值
	double BestValue;                 //最优路径长度
}TSP, *PTSP;

void CalculatDist()
{//计算每两个城市之间的距离
	int i, j;
	int temp1, temp2;
	for (i = 0; i < CITY_NUM; i++) {
		for (j = 0; j <= CITY_NUM; j++) {       
			temp1 = CityPos[j].x - CityPos[i].x;
			temp2 = CityPos[j].y - CityPos[i].y;
			CityDistance[i][j] = sqrt(temp1*temp1 + temp2 * temp2);
		}
	}
}

void copy(int a[], int b[])
{//复制
	int i = 0;
	for (i = 0; i < CITY_NUM + 1; i++)
	{
		a[i] = b[i];
	}
}

bool check(TSP &city, int pop, int num, int k)
{//用来检查新生成的节点是否在当前群体中,0号节点是默认出发节点和终止节点
	int i;
	for (i = 0; i <= num; i++) {
		if (k == city.colony[pop][i])
			return true;//新生成节点存在于已经生成的路径中
	}
	return false;//新生成节点没有存在于已经生成的路径中
}

void InitColony(TSP &city)
{//初始化种群
	int i, j, r;
	for (i = 0; i < POPSIZE; i++) {
		city.colony[i][0] = 0;
		city.colony[i][CITY_NUM] = 0;
		city.BestValue = MAXVALUE;
		city.BestFitness = 0;//适应值越大越好
	}
	for (i = 0; i < POPSIZE; i++)
	{
		for (j = 1; j < CITY_NUM; j++)
		{
			r = rand() % (CITY_NUM - 1) + 1;//产生1~CITY_NUM-1之间的随机数
			while (check(city, i, j, r))
			{
				r = rand() % (CITY_NUM - 1) + 1;
			}
			city.colony[i][j] = r;
		}
	}
}

void CalFitness(TSP &city)
{//计算适应值
	int i, j, start, end;
	int Best = 0;
	for (i = 0; i < POPSIZE; i++) {//求适应值
		city.Distance[i] = 0;
		for (j = 1; j <= CITY_NUM; j++)
		{
			start = city.colony[i][j - 1]; end = city.colony[i][j];
			city.Distance[i] = city.Distance[i] + CityDistance[start][end];
		}
		city.fitness[i] = pow(2, N / (city.Distance[i]));
		if (city.fitness[i] > city.fitness[Best])
			Best = i;
	}
	copy(city.BestRooting, city.colony[Best]);
	city.BestFitness = city.fitness[Best];
	city.BestValue = city.Distance[Best];
}

void Select(TSP &city)
{//选择算子
	int TempColony[POPSIZE][CITY_NUM + 1];
	int i, j, s, t;
	double GaiLv[POPSIZE];
	int SelectP[POPSIZE + 1];
	double avg;
	double sum = 0;
	for (i = 0; i < POPSIZE; i++)
	{
		sum += city.fitness[i];
	}
	for (i = 0; i < POPSIZE; i++)
	{
		GaiLv[i] = city.fitness[i] / sum;
	}
	SelectP[0] = 0;
	for (i = 0; i < POPSIZE; i++)
	{
		SelectP[i + 1] = SelectP[i] + GaiLv[i] * RAND_MAX;
	}
	for (t = 0; t < POPSIZE; t++)
	{//轮盘赌
		s = rand() % RAND_MAX;
		for (i = 1; i < POPSIZE; i++)
		{
			if (SelectP[i] >= s)
				break;
		}
		copy(TempColony[t], city.colony[i - 1]);
	}
	for (i = 0; i < POPSIZE; i++)
	{
		copy(city.colony[i], TempColony[i]);
	}
}

void Cross(TSP &city, double pc)
{//交叉算子
	int i, j, t;
	int a, b;
	int Temp1[CITY_NUM + 1], Temp2[CITY_NUM + 1];
	for (i = 0; i < POPSIZE - 1; i++)
	{
		double s = ((double)(rand() % RAND_MAX)) / RAND_MAX;
		if (s < pc)
		{
			a = rand() % (CITY_NUM - 4) + 1;
			memset(Hash, 0, sizeof(Hash));//内存空间初始化
			Temp1[0] = Temp1[CITY_NUM] = 0;
			for (j = 1; j < 5; j++)
			{
				Temp1[j] = city.colony[i + 1][a + j - 1];
				Hash[Temp1[j]] = 1;
			}
			for (t = 1; t < CITY_NUM; t++)
			{
				if (Hash[city.colony[i][t]] == 0)
				{
					Temp1[j++] = city.colony[i][t];
					Hash[city.colony[i][t]] = 1;
				}
			}
			memset(Hash, 0, sizeof(Hash));
			Temp2[0] = Temp2[CITY_NUM] = 0;
			for (j = 1; j < 5; j++)
			{
				Temp2[j] = city.colony[i][a + j - 1];
				Hash[Temp2[j]] = 1;
			}
			for (t = 1; t < CITY_NUM; t++)
			{
				if (Hash[city.colony[i + 1][t]] == 0)
				{
					Temp2[j++] = city.colony[i + 1][t];
					Hash[city.colony[i + 1][t]] = 1;
				}
			}
			copy(city.colony[i], Temp1);
			copy(city.colony[i + 1], Temp2);
		}
	}

}

double GetDistance(int a[CITY_NUM + 1])
{
	int i, start, end;
	double Distance = 0;
	for (i = 0; i < CITY_NUM; i++)
	{
		start = a[i];   end = a[i + 1];
		Distance += CityDistance[start][end];
	}
	return Distance;
}

void Mutation(TSP &city, double pm)
{//变异算子
	int i;
	int Temp[CITY_NUM + 1];
	for (i = 0; i < POPSIZE; i++)
	{
		double s = ((double)(rand() % RAND_MAX)) / RAND_MAX;
		if (s < pm)
		{
			int a, b, t;
			a = (rand() % (CITY_NUM - 1)) + 1;
			b = (rand() % (CITY_NUM - 1)) + 1;
			copy(Temp, city.colony[i]);
			t = Temp[a];
			Temp[a] = Temp[b];
			Temp[b] = t;
			if (GetDistance(Temp) > GetDistance(city.colony[i]))
			{
				a = (rand() % (CITY_NUM - 1)) + 1;
				b = (rand() % (CITY_NUM - 1)) + 1;
				copy(Temp, city.colony[i]);
				t = Temp[a];
				Temp[a] = Temp[b];
				Temp[b] = t;
			}
			copy(city.colony[i], Temp);
		}
	}
}

void OutPut(TSP &city)
{
	int i, j;
	printf("The population is:\n");
	for (i = 0; i < POPSIZE; i++)
	{
		for (j = 0; j <= CITY_NUM; j++)
		{
			printf("%5d", city.colony[i][j]);
		}
		printf("    %f\n", city.Distance[i]);
	}
	printf("The best rooting is:\n");
	for (i = 0; i <= CITY_NUM; i++)
		printf("%5d", city.BestRooting[i]);
	printf("\nIt's cost value is %f\n", (city.BestValue));
	printf("\n\n\n\n");
}

int main()
{
	TSP city;
	double pcross = 0.5, pmutation = 0.1;//交叉概率和变异概率
	int MaxEpoc = 200;//最大迭代次数
	int i;
	srand((unsigned)time(0));//随机数发生器的初始化
	CalculatDist();//求城市间两两之间的距离
	InitColony(city);//生成初始种群
	CalFitness(city);//计算适应值,考虑应该在这里面把最优选出来
	for (i = 0; i < MaxEpoc; i++)
	{
		Select(city);//选择(复制)
		Cross(city, pcross);//交叉
		Mutation(city, pmutation);//变异
		CalFitness(city);//计算适应值		
	}
	OutPut(city);//输出
	return 0;
}