吴晓春 摘 要 用C#语言来实现遗传算法的通用代码GA类,然后调动GA类解决组合优化领域的经典问题TSP,并给出了该算法的实现程序。 关键词 遗传算法,TSP问题 遗传算法的历史起源可追溯至60年代,Holland于1975年出版的著名著作《自然系统和人工系统的适配》系统地阐述了遗传算法的基本理论和方法,并提出了对遗传算法的理论研究和发展极为重要的模式理论。这一理论首次确认了结构重组遗传操作对于获得隐并行性的重要性。 进入80年代,遗传算法迎来了兴盛发展时期,无论是理论研究还是应用研究都成了十分热门的课题。 一、遗传算法的C# 实现 遗传算法是模拟生物遗传进化的过程,在全局优化过程中找到最优解或其近似解的有效算法。简单地说,让多个随机解不断地繁殖,每一代解不断地进化,最后得到最优解或近似最优解。解决某一问题的遗传算法的实现过程有以下几个步骤: (1) 初始:随机产生待解决问题的若干解,每个解称为染色体(又称个体),所有解称为群体。 (2) 适应度:计算群体中每个染色体的适应度,代表解的优化程度。 (3) 新一代群体:通过下列步骤的循环产生新一代的群体,称子代群体。即新的若干个解。 1) 选择:按照适应度选择两个父代染色体作为双亲染色体。适应度高,被选择的几率就高。 2) 交叉:按照交叉率由双亲染色体交叉形成两个新一代染色体, 若交叉不能完成,直接由双亲复制产生两个子代染色体。 3) 变异:按照变异率让染色体的某个基因产生突变形成新的染色体。 4) 替换:使用新的群体代替原群体。即新的若干个解代替原来的若干个解。 (4) 测试:如果群体繁殖的次数已经足够,返回群体中的适应度最优的个体作为解。 (5) 循环:返回到第2步 上述步骤中,群体初始化、适应度计算、选择、交叉、变异都与具体的问题紧紧联系,其代码受具体问题的制约。为了通用性,在实现遗传算法时,用代理代表这些功能。遗传算法的通用C#代码包括三个类,分别为基因类(GAGene)、染色体类(GAChromosome)、遗传算法类(GA)。 基因类GAGene由构造方法和Value属性组成,Value是字串型,对应实际问题的最基本信息。染色体类GAChromosome继承ArrayList来实现,由若干基因顺序组成。遗传算法类GA实现遗传算法的总体功能。 初始群体的产生是采用随机的方法产生问题的多个解,核心代码如下: public void Initialize()//产生第一代群体 { try{ if (this.EnableLogging && this.LogFilePath != "") System.IO.File.Delete(LogFilePath);//染色体数据存盘 if (this.EnableLogging) { AddToLogFile("序号/t染色体/t适应度"); AddToLogFile("--------------------------------------------------"); } } catch (System.IO.IOException exp){} m_thisGeneration = new System.Collections.ArrayList();//当前群体, for (int i = 0; i < PopulationSize; i++) //PopulationSize为群体大小,即染色体个数 { GAChromosome newParent = new GAChromosome(m_init, m_fit, m_mutate); //利用m_init生成染色体,并用m_fit计算染色体的适应度,染色体变异时使用m_mutate m_thisGeneration.Add(newParent);//添加到群体中 if (this.EnableLogging)AppendToLogFile(i, newParent);//数据存盘 } RankPopulation();//根据适应度对染色体排序 } 在构造染色体new GAChromosome(m_init, m_fit, m_mutate)时,通过三个参数的代理方法来生成染色体、计算适应度、实现变异。具体代码随具体问题而不同。 新一代群体,又称为子代群体,它是在父代群体中选择优秀的染色体经过交叉、变异产生的。为了保证子代群体比父代群体在整体上更为优秀,选择方法、交叉方法和变异方法都要根据具体问题来设计调整,基于子代比父代比更优秀的趋势,群体向着最优解靠近,通过不断的循环(繁殖),总能找到最优解或其近似解。核心代码如下: public void CreateNextGeneration()//生成新一代群体 { m_NextGeneration.Clear(); GAChromosome bestChromo = null; if (this.ApplyElitism) // 最优秀(适度度最好)的染色体是否直接选择加入到新一代群体 bestChromo = (GAChromosome)m_thisGeneration[0];//取出最优染色体(已排序) for (int i = 0; i < this.PopulationSize; i += 2)//逐步生成新一代群体,每次生成两个 { //Step 1 选择:选择父代中的两个优秀的染色体 int iDadParent = 0; int iMumParent = 0; switch (this.SelectionType) //选择的方法有:轮赌、竞争、等级等等 { case Selection.Tournment: //竞争法 iDadParent = TournamentSelection(); iMumParent = TournamentSelection(); break; case Selection.Roullette: //轮赌法 iDadParent = RouletteSelection(); iMumParent = RouletteSelection(); break; } GAChromosome Dad = (GAChromosome)m_thisGeneration[iDadParent]; GAChromosome Mum = (GAChromosome)m_thisGeneration[iMumParent]; GAChromosome child1 = new GAChromosome(this.m_fit, this.m_mutate); GAChromosome child2 = new GAChromosome(this.m_fit, this.m_mutate); //Step 2 交叉:参照交叉率随机决定,是交叉产生两个下一代的染色体还是直接产生 if (m_Random.NextDouble() < this.CrossOver) { m_crossOver(Dad, Mum, ref child1, ref child2); } else { child1 = Dad; child2 = Mum; } //Step 3 变异:参照变异率决定是否变异 if (m_Random.NextDouble() < this.Mutation) { m_mutate(child1); m_mutate(child2); } //Step 4 计算适应度 this.m_fit(child1); this.m_fit(child2); m_NextGeneration.Add(child1); m_NextGeneration.Add(child2); }//for 子代群体生成结束 if (null != bestChromo) m_NextGeneration.Insert(0, bestChromo); //最优的染色体插入子代群体中 m_thisGeneration.Clear(); //用新一代替换当代群体 for (int j = 0; j < this.PopulationSize; j++) { m_thisGeneration.Add(m_NextGeneration[j]);} this.RankPopulation();//按照适应度对染色体排序 this.GenerationNum++;//优化过程计数 } 优秀的染色体是指适应度高的染色体。群体中最优的那个染色体将毫无改变的复制到新的群体中。用比较优秀的染色体产生子代,才能保证子代更优秀,以达到全局优化的目的。选择优秀染色体的方法较多,如轮赌选择法、竞赛(或称竞争)选择法、分级选择法、稳定状态选择法等这里只介绍轮赌选择法和竞赛选择法。 轮赌选择法:父代的选择是根据他们的适应度做出的。染色体适应度越高,那么它被选择到的机会就越大。想像一个轮盘赌的机器上放置了群体中所有的染色体。每一个染色体所占的地方的大小和它的适应度成正比。然后开始扔弹子,扔到那个地方就把对应的染色体选择出来。显然,适应度越大的染色体被选到的机会就越大。 这个过程可以用下面的这个算法来模拟:计算所有染色体的适应度之和S (大写);在区间(0,S)上随机的产生一个数r;在群体中从适应度最小的染色体开始,把它的适应度加到s (小写)上去(s开始为0),如果s大于r,则停止循环并返回当前染色体,核心代码如下: |