您的位置:首页 > 其它

TSP问题的遗传算法解法

2009-07-14 18:25 447 查看
document.domain = "csdn.net";
TSP
问题的遗传算法解法

TSP
问题的意思是:

给定几个城市,旅行商必须决定一条最短的路线,使他能够访问到每个城市一次,然后返回到他的起点。

TSP
问题是一个非常具有迷惑性的简单问题。它的主要难度来源于此:当城市数目不断增加时,求解问题所需要的计算机力呈指数级增长。这意味着,假设在某台计算机上某个算法可以解决

50
个城市的

TSP
,但你再增加

10
个城市,采用同一个算法,就需要快一千倍的计算机才能解决它。在任何高速的机器上,采用同一算法,都会很快达到它的极限。

TSP
问题的主要特点是:任何解都是问题中所有城市的一个置换或者重新排列,因此,必须确保所有的基因组都代表一个有效的排列,也就是对所有城市的一个有效周游。在这里,我们选择整数表示城市,一个有效的周游就是维数为城市数目,而且元素两两互不相同的一个整数矢量。

1.相关类。

1.1 CmapTSP(类)

为了封装地图数据,城市座标以及适应性计算,创建CmapTSP 类。其中CoOrd是一个保存每个城市的x和y座标的简单结构。

class CmapTSP
{
private static double pi = 3.1415926535897;
private IList<CoOrd> m_vecCityCoOrds = new List<CoOrd>();
//number of cities in the problem
private int m_NumCities = 18;
//client window dimensions
private int m_MapWidth;
private int m_MapHeight;
//holds the length of the solution, if one is calculable.
double m_dBestPossibleRoute;
public CmapTSP()
{
this.CreateCitiesCircular();
this.CalculateBestPossibleRoute();
}
//calculate the coordinates of each city (positioned in a circle)
public void CreateCitiesCircular()
{
//first caculate the radius of spread and the origin
int margin = 50;
double radius;
if (m_MapHeight < m_MapWidth)
{
radius = (m_MapHeight / 2) - margin;
}
else
{
radius = (m_MapWidth / 2) - margin;
}
CoOrd origin = new CoOrd(m_MapWidth / 2, m_MapHeight / 2);
//calculate angle division between adjacent cities.
double SegmentSize = 2 * pi / m_NumCities;
double angle = 0;
//  vector<CoOrd> vecCities;
while (angle < 2 * pi)
{
CoOrd ThisCity = new CoOrd();
ThisCity.x = (float)(radius * Math.Sin(angle) + origin.x);
ThisCity.y = (float)(radius * Math.Cos(angle) + origin.y);
m_vecCityCoOrds.Add(ThisCity);
angle += SegmentSize;
}
}
//calculates the distance between two cities
public double CalculateA_to_B(CoOrd city1, CoOrd city2)
{
double xDist = city1.x - city2.x;
double yDist = city1.y - city2.y;
return Math.Sqrt(xDist * xDist + yDist * yDist);
}
public void CalculateBestPossibleRoute()
{
m_dBestPossibleRoute = 0;
for (int city = 0; city < m_vecCityCoOrds.Count - 1; ++city)
{
m_dBestPossibleRoute += CalculateA_to_B(m_vecCityCoOrds[city], m_vecCityCoOrds[city + 1]);
//add in a small amount to cover any precision errors we may have made
}
//add in the distance from the last to the first
m_dBestPossibleRoute += CalculateA_to_B(m_vecCityCoOrds[m_vecCityCoOrds.Count - 1], m_vecCityCoOrds[0]);
// m_dBestPossibleRoute = 480;
}
//calculates the tour length
public double GetTourLength(IList<int> route)
{
double TotalDistance = 0;
for (int i = 0; i < route.Count - 1; ++i)
{
int city1 = route[i];
int city2 = route[i + 1];
TotalDistance += CalculateA_to_B(m_vecCityCoOrds[city1], m_vecCityCoOrds[city2]);
}
//don't forget this is a closed loop so we need to add in the distance
//from the last city visited back to the first
int last = route[route.Count - 1];
int first = route[0];
TotalDistance += CalculateA_to_B(m_vecCityCoOrds[last], m_vecCityCoOrds[first]);
return TotalDistance;
}
}


1.2 SGenome(类)

基因组sgenome结构定义如下:

public  class SGenome
{
IList<int> _vecCities;
//its fitness
private  double _dFitness;
private static Random rand = new Random();
public SGenome()
{
_vecCities = new List<int>();
}
public SGenome(int nc)
{
_vecCities = GrabPermutation(nc);
}
//creates a random tour of the cities
private IList<int> GrabPermutation(int limit)
{
IList<int> vecPerm = new List<int>();
for (int i = 0; i < limit; i++)
{
//we use limit-1 because we want ints numbered from zero
int NextPossibleNumber = rand.Next(0, limit);
while (TestNumber(vecPerm, NextPossibleNumber))
{
NextPossibleNumber = rand.Next(0, limit);
}
vecPerm.Add(NextPossibleNumber);
}
return vecPerm;
}
//used in GrabPermutation
public bool TestNumber(IList<int> vec, int number)
{
for (int i = 0; i < vec.Count; ++i)
{
if (vec[i] == number)
{
return true;
}
}
return false;
}
}


1.3 CgaTSP(类)

这个是遗传算法类。代码太长,只贴出主要的方法的代码。

//---------------------------MutateEM-----------------------------
//
//	Mutates the chromosome by choosing two random genes and swapping
//	their position.
//-----------------------------------------------------------------
public void MutateEM(IList<int> chromo)
{
//return dependent upon mutation rate
if (rand.NextDouble() > m_dMutationRate)
{
return;
}
//choose first gene
int pos1 = rand.Next(0, chromo.Count);
//choose second
int pos2 = pos1;
while (pos1 == pos2)
{
pos2 = rand.Next(0, chromo.Count);
}
//swap their positions
swap(chromo, chromo[pos1], chromo[pos2]);
}
public void swap(IList<int> list, int i, int j)
{
int index1 = list.IndexOf(i);
int index2 = list.IndexOf(j);
list[index1] = j;
list[index2] = i;
}
//-------------------------CrossoverPMX---------------------------------
//
// crossover operator based on 'partially matched crossover' as
// defined in the text
//-------------------------------------------------------------------
public void CrossoverPMX(IList<int> mum, IList<int> dad, IList<int> baby1, IList<int> baby2)
{
foreach (int m in mum)
{
baby1.Add(m);
}
foreach (int m in dad)
{
baby2.Add(m);
}
//just return dependent on the crossover rate or if the
//chromosomes are the same.
if ((rand.NextDouble() > m_dCrossoverRate) || (mum == dad))
{
return;
}
//first we choose a section of the chromosome
int beg = rand.Next(0, mum.Count - 1);
int end = beg;
//find an end
while (end <= beg)
{
end = rand.Next(0, mum.Count);
}
//now we iterate through the matched pairs of genes from beg
//to end swapping the places in each child
for (int pos = beg; pos < end + 1; ++pos)
{
//these are the genes we want to swap
int gene1 = mum[pos];
int gene2 = dad[pos];
if (gene1 != gene2)
{
swap(baby1, gene1, gene2);
swap(baby2, gene1, gene2);
}
}//next pair
}
public SGenome RouletteWheelSelection()
{
double fSlice = rand.NextDouble() * m_dTotalFitness;
double cfTotal = 0.0;
int SelectedGenome = 0;
for (int i = 0; i < m_iPopSize; ++i)
{
cfTotal += m_vecPopulation[i].dFitness;
if (cfTotal > fSlice)
{
SelectedGenome = i;
break;
}
}
return m_vecPopulation[SelectedGenome];
}
//methods used in the fitness functions
public void CalculatePopulationsFitness()
{
//for each chromo
for (int i = 0; i < m_iPopSize; ++i)
{
//calculate the tourlength for each chromosome
double TourLength = m_pMap.GetTourLength(m_vecPopulation[i].vecCities);
m_vecPopulation[i].dFitness = TourLength;
//keep a track of the shortest route found each generation
if (TourLength < m_dShortestRoute)
{
m_dShortestRoute = TourLength;
m_iFittestGenome = i;
}
//keep a track of the worst tour each generation
if (TourLength > m_dLongestRoute)
{
m_dLongestRoute = TourLength;
}
}//next chromo
//Now we have calculated all the tour lengths we can assign
//the fitness scores
for (int j = 0; j < m_iPopSize; ++j)
{
m_vecPopulation[j].dFitness = m_dLongestRoute - m_vecPopulation[j].dFitness;
m_dTotalFitness += m_vecPopulation[j].dFitness;
}
}
public void Epoch()
{
//first reset variables and calculate the fitness of each genome
Reset();
CalculatePopulationsFitness();
if ((m_dShortestRoute <= m_pMap.BestPossibleRoute()))
{
m_bStarted = false;
return;
}
//create a temporary vector for the new population
IList<SGenome> vecNewPop = new List<SGenome>();
//First add NUM_BEST_TO_ADD number of the last generation's
//fittest genome(elitism)
// NUM_BEST_TO_ADD = 2
for (int i = 0; i < 2; ++i)
{
vecNewPop.Add(m_vecPopulation[m_iFittestGenome]);
}
//now create the remainder of the population
while (vecNewPop.Count != m_iPopSize)
{
//grab two parents
SGenome mum = RouletteWheelSelection();
SGenome dad = RouletteWheelSelection();
//create 2 children
SGenome baby1 = new SGenome();
SGenome baby2 = new SGenome();
//Breed them
CrossoverPMX(mum.vecCities, dad.vecCities, baby1.vecCities, baby2.vecCities);
//and mutate them
MutateEM(baby1.vecCities);
MutateEM(baby2.vecCities);
//add them to new population
vecNewPop.Add(baby1);
vecNewPop.Add(baby2);
}
//copy into next generation
m_vecPopulation = vecNewPop;
//increment generation counter
++m_iGeneration;
}


2 其他需要注意的问题

2.1
选择一个适应性分数

需要一个适应性函数,能够为愈短的周游线路奖励愈高的分数。可以用周游路程长度作为此函数,但这样得到的数值分布的广度不够,使群中最好的和最差的染色体的分数差别不大。因此,采用适用性比例选择法时,使适应性分数高的基因组被选上是相当困难的。一种比较理想的办法是:记下每一代中的最差的周游路线长度,然后再对种群基因组作一轮循环,用最长的路程(即最差的)减去每个基因组的路程,得到的为基因组的适应性分数。这样做可以使结果的相对差异变大,使有轮盘赌选择时也会更有效。这样做也可以从种群中移除最差的染色体,因为它的适应性分数为0,在选择时根本不会被选中。

2.2 杂交和变异

本次选择的杂交方法是部分映射杂交,变异方法是交换变异。它们的具体方法,我会在下一篇《遗传算法优化》中详细讲解。

2.3 选择

这里还是采用轮盘赌选择法,但是仍有一点不同,为了使遗传算法能够较快的收敛,在每一个epoch中,在选择循环前,都就确保将前一代的中适应性最高的2的基因原样复制到新一代中,这代表适应性最好的基本组永远不会在随机过程上丢掉。这一技术通常称之为种子或者精英选择法。

3 本机运行结果

还是那句话,我应该学习一下C++的。

运行中:



运行结果:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: