点击上方“ 剑指数模 ”,选择“ 置顶 / 星标 ”

回复『进群』,加入高手如云

关于遗传算法,弈笙在一篇博客中看到了比较形象有趣的讲法,适合初学者理解,并用C语言为例重写遗传算法,在这里整理分享给大家,文章比较长,耐心看完你一定会有收获。

科学定义

遗传算法(Genetic Algorithm, GA)起源于对生物系统所进行的计算机模拟研究。它是模仿自然界生物进化机制发展起来的随机全局搜索和优化方法,借鉴了达尔文的进化论和孟德尔的遗传学说。其本质是一种高效、并行、全局搜索的方法,能在搜索过程中自动获取和积累有关搜索空间的知识,并自适应地控制搜索过程以求得最佳解。

相关术语

  • 基因型(genotype):性状染色体的内部表现;

  • 表现型(phenotype):染色体决定的性状的外部表现,或者说,根据基因型形成的个体的外部表现;

  • 进化(evolution):种群逐渐适应生存环境,品质不断得到改良。生物的进化是以种群的形式进行的。

  • 适应度(fitness):度量某个物种对于生存环境的适应程度。

  • 选择(selection):以一定的概率从种群中选择若干个个体。一般,选择过程是一种基于适应度的优胜劣汰的过程。

  • 复制(reproduction):细胞分裂时,遗传物质DNA通过复制而转移到新产生的细胞中,新细胞就继承了旧细胞的基因。

  • 交叉(crossover):两个染色体的某一相同位置处DNA被切断,前后两串分别交叉组合形成两个新的染色体。也称基因重组或杂交;

  • 变异(mutation):复制时可能(很小的概率)产生某些复制差错,变异产生新的染色体,表现出新的性状。

  • 编码(coding):DNA中遗传信息在一个长链上按一定的模式排列。遗传编码可看作从表现型到基因型的映射。

  • 解码(decoding):基因型到表现型的映射。

  • 个体(individual):指染色体带有特征的实体;

  • 种群(population):个体的集合,该集合内个体数称为种群的大小。

遗传算法的有趣应用很多,诸如寻路问题,8数码问题,囚犯困境,动作控制,找圆心问题(在一个不规则的多边形中,寻找一个包含在该多边形内的最大圆圈的圆心),TSP问题,生产调度问题,人工生命模拟等。下面我以袋鼠为例子讲讲遗传算法。(因为袋鼠会跳)

袋鼠跳问题

遗传算法中每一条染色体,对应着遗传算法的一个解决方案,一般我们用适应性函数(fitness function)来衡量这个解决方案的优劣。所以从一个基因组到其解的适应度形成一个映射。

可以把遗传算法的过程看作是一个在多元函数里面求最优解的过程。可以这样想象,这个多维曲面里面有数不清的“山峰”,而这些山峰所对应的就是局部最优解。而其中也会有一个“山峰”的海拔最高的,那么这个就是全局最优解。

而遗传算法的任务就是尽量爬到最高峰,而不是陷落在一些小山峰。(另外,值得注意的是遗传算法不一定要找“最高的山峰”,如果问题的适应度评价越小越好的话,那么全局最优解就是函数的最小值,对应的,遗传算法所要找的就是“最深的谷底”)


既然我们把函数曲线理解成一个一个山峰和山谷组成的山脉。那么我们可以设想所得到的每一个解就是一只袋鼠,我们希望它们不断的向着更高处跳去,直到跳到最高的山峰(尽管袋鼠本身不见得愿意那么做)。所以求最大值的过程就转化成一个“袋鼠跳”的过程。

作为对比下面简单介绍“袋鼠跳”的几种方式:

  1. 爬山法(最速上升爬山法):
    从搜索空间中随机产生邻近的点,从中选择对应解最优的个体,替换原来的个体,不断重复上述过程。因为爬山法只对“邻近”的点作比较,所以目光比较“短浅”,常常只能收敛到离开初始位置比较近的局部最优解上面。对于存在很多局部最优点的问题,通过一个简单的迭代找出全局最优解的机会非常渺茫。(在爬山法中,袋鼠最有希望到达最靠近它出发点的山顶,但不能保证该山顶是珠穆朗玛峰,或者是一个非常高的山峰。因为一路上它只顾上坡,没有下坡。)

  2. 模拟退火:
    这个方法来自金属热加工过程的启发。在金属热加工过程中,当金属的温度超过它的熔点(Melting Point)时,原子就会激烈地随机运动。与所有的其它的物理系统相类似,原子的这种运动趋向于寻找其能量的极小状态。在这个能量的变迁过程中,开始时,温度非常高, 使得原子具有很高的能量。随着温度不断降低,金属逐渐冷却,金属中的原子的能量就越来越小,最后达到所有可能的最低点。利用模拟退火的时候,让算法从较大的跳跃开始,使到它有足够的“能量”逃离可能“路过”的局部最优解而不至于限制在其中,当它停在全局最优解附近的时候,逐渐的减小跳跃量,以便使其“落脚 ”到全局最优解上。(在模拟退火中,袋鼠喝醉了,而且随机地大跳跃了很长时间。运气好的话,它从一个山峰跳过山谷,到了另外一个更高的山峰上。但最后,它渐渐清醒了并朝着它所在的峰顶跳去。)

  3. 遗传算法:
    模拟物竞天择的生物进化过程,通过维护一个潜在解的群体执行了多方向的搜索,并支持这些方向上的信息构成和交换。是以面为单位的搜索,比以点为单位的搜索,更能发现全局最优解。(在遗传算法中,有很多袋鼠,它们降落到喜玛拉雅山脉的任意地方。这些袋鼠并不知道它们的任务是寻找珠穆朗玛峰。但每过几年,就在一些海拔高度较低的地方射杀一些袋鼠,并希望存活下来的袋鼠是多产的,在它们所处的地方生儿育女。)(或者换个说法。从前,有一大群袋鼠,它们被莫名其妙的零散地遗弃于喜马拉雅山脉。于是只好在那里艰苦的生活。海拔低的地方弥漫着一种无色无味的毒气,海拔越高毒气越稀薄。可是可怜的袋鼠们对此全然不觉,还是习惯于活蹦乱跳。于是,不断有袋鼠死于海拔较低的地方,而越是在海拔高的袋鼠越是能活得更久,也越有机会生儿育女。就这样经过许多年,这些袋鼠们竟然都不自觉地聚拢到了一个个的山峰上,可是在所有的袋鼠中,只有聚拢到珠穆朗玛峰的袋鼠被带回了美丽的澳洲。)

遗传算法的实现

遗传算法的实现过程实际上就像自然界的进化过程那样。首先寻找一种对问题潜在解进行“数字化”编码的方案。(建立表现型和基因型的映射关系)然后用随机数初始化一个种群(那么第一批袋鼠就被随意地分散在山脉上),种群里面的个体就是这些数字化的编码。

接下来,通过适当的解码过程之后(得到袋鼠的位置坐标),用适应性函数对每一个基因个体作一次适应度评估(袋鼠爬得越高,越是受我们的喜爱,所以适应度相应越高)。用选择函数按照某种规定择优选择(我们要每隔一段时间,在山上射杀一些所在海拔较低的袋鼠,以保证袋鼠总体数目持平。)。让个体基因变异(让袋鼠随机地跳一跳)。然后产生子代(希望存活下来的袋鼠是多产的,并在那里生儿育女)。

遗传算法并不保证你能获得问题的最优解,但是使用遗传算法的最大优点在于你不必去了解和操心如何去“找”最优解。(你不必去指导袋鼠向那边跳,跳多远。)而只要简单的“否定”一些表现不好的个体就行了。(把那些总是爱走下坡路的袋鼠射杀,这就是遗传算法的精粹!)

所以我们总结出遗传算法的一般步骤:

开始循环直至找到满意的解。

  1. 评估每条染色体所对应个体的适应度。

  2. 遵照适应度越高,选择概率越大的原则,从种群中选择两个个体作为父方和母方。

  3. 抽取父母双方的染色体,进行交叉,产生子代。

  4. 对子代的染色体进行变异。

  5. 重复2,3,4步骤,直到新种群的产生。

结束循环。


让我们先来考虑考虑下面这个问题的解决办法。


现在要求在既定的区间内找出函数的最大值

接下来,我们将详细地剖析遗传算法过程的每一个细节。

编制袋鼠的染色体----基因的编码方式

受到人类染色体结构的启发,我们可以设想一下,假设目前只有“0”,“1”两种碱基,我们也用一条链条把他们有序的串连在一起,因为每一个单位都能表现出 1 bit的信息量,所以一条足够长的染色体就能为我们勾勒出一个个体的所有特征。这就是二进制编码法,染色体大致如下:

010010011011011110111110

上面的编码方式虽然简单直观,但明显地,当个体特征比较复杂的时候,需要大量的编码才能精确地描述,相应的解码过程(类似于生物学中的DNA翻译过程,就是把基因型映射到表现型的过程。)将过分繁复,为改善遗传算法的计算复杂性、提高运算效率,提出了浮点数编码。染色体大致如下:

1.2 –3.3 – 2.0 –5.4 – 2.7 – 4.3

(注:还有一种编码方式叫符号编码)

那么我们如何利用这两种编码方式来为袋鼠的染色体编码呢?因为编码的目的是建立表现型到基因型的映射关系,而表现型一般就被理解为个体的特征。比如人的基因型是46条染色体所描述的却能解码成一个眼,耳,口,鼻等特征各不相同的活生生的人。所以我们要想为“袋鼠”的染色体编码,我们必须先来考虑“袋鼠”的“个体特征”是什么。也许有的人会说,袋鼠的特征很多,比如性别,身长,体重,也许它喜欢吃什么也能算作其中一个特征。但具体在解决这个问题的情况下,我们应该进一步思考:无论这只袋鼠是长短,肥瘦,黑白只要它在低海拔就会被射杀,同时也没有规定身长的袋鼠能跳得远一些,身短的袋鼠跳得近一些。当然它爱吃什么就更不相关了。我们由始至终都只关心一件事情:袋鼠在哪里。因为只要我们知道袋鼠在那里,我们就能做两件必须去做的事情:

(1)通过查阅喜玛拉雅山脉的地图来得知袋鼠所在的海拔高度(通过自变量求适应函数的值。)以判断我们有没必要把它射杀。

(2)知道袋鼠跳一跳(交叉和变异)后去到哪个新位置。

如果我们一时无法准确的判断哪些“个体特征”是必要的,哪些是非必要的,我们常常可以用到这样一种思维方式:比如你认为袋鼠的爱吃什么东西非常必要,那么你就想一想,有两只袋鼠,它们其它的个体特征完全同等的情况下,一只长得黑,另外一只长得不是那么黑。你会马上发现,这不会对它们的命运有丝毫的影响,它们应该有同等的概率被射杀!只因它们处于同一个地方。(值得一提的是,如果你的基因编码设计中包含了袋鼠黑不黑的信息,这其实不会影响到袋鼠的进化的过程,而那只攀到珠穆朗玛峰的袋鼠黑与白什么的也完全是随机的,但是它所在的位置却是非常确定的。)

以上是对遗传算法编码过程中经常经历的思维过程,必须把具体问题抽象成数学模型,突出主要矛盾,舍弃次要矛盾。只有这样才能简洁而有效的解决问题。

既然确定了袋鼠的位置作为个体特征,具体来说位置就是横坐标。那么接下来,我们就要建立表现型到基因型的映射关系。就是说如何用编码来表现出袋鼠所在的横坐标。由于横坐标是一个实数,所以说透了我们就是要对这个实数编码。回顾我们上面所介绍的两种编码方式,最先想到的应该就是,对于二进制编码方式来说,编码会比较复杂,而对于浮点数编码方式来说,则会比较简洁。恩,正如你所想的,用浮点数编码,仅仅需要一个浮点数而已。而下面则介绍如何建立二进制编码到一个实数的映射。

明显地,一定长度的二进制编码序列,只能表示一定精度的浮点数。譬如我们要求解精确到六位小数,由于区间长度为2 – (-1) = 3 ,为了保证精度要求,至少把区间[-1,2]分为3 × 10^6等份。又因为


所以编码的二进制串至少需要22位。

把一个二进制串(b0,b1,….bn)转化位区间里面对应的实数值通过下面两个步骤。

  1. 将一个二进制串代表的二进制数转化为10进制数:


  2. 对应区间内的实数:


二进制串00000000000000000000001111111111111111111111则分别表示区间的两个端点值-1和2。

好了,目前为止我们把袋鼠的染色体给研究透了,让我们继续跟进袋鼠的进化旅程

物竞天择--适应性评分与及选择函数。

  1. 物竞――适应度函数(fitness function)
    自然界生物竞争过程往往包含两个方面:生物相互间的搏斗与及生物与客观环境的搏斗过程。但在我们这个实例里面,你可以想象到,袋鼠相互之间是非常友好的,它们并不需要互相搏斗以争取生存的权利。它们的生死存亡更多是取决于你的判断。因为你要衡量哪只袋鼠该杀,哪只袋鼠不该杀,所以你必须制定一个衡量的标准。而对于这个问题,这个衡量的标准比较容易制定:袋鼠所在的海拔高度。(因为你单纯地希望袋鼠爬得越高越好。)所以我们直接用袋鼠的海拔高度作为它们的适应性评分。即适应度函数直接返回函数值就行了。

  2. 天择――选择函数(selection)
    自然界中,越适应的个体就越有可能繁殖后代。但是也不能说适应度越高的就肯定后代越多,只能是从概率上来说更多。(毕竟有些所处海拔高度较低的袋鼠很幸运,逃过了你的眼睛。)那么我们怎么来建立这种概率关系呢?下面我们介绍一种常用的选择方法――轮盘赌(Roulette Wheel Selection)选择法。

比如我们有5条染色体,他们所对应的适应度评分分别为:5,7,10,13,15。所以累计总适应度为:


所以各个个体被选中的概率分别为:



你可以想象一下,我们转动轮盘,轮盘停下来的时候,指针会随机地指向某一个个体所代表的区域,那么非常幸运地,这个个体被选中了。(很明显,适应度评分越高的个体被选中的概率越大。)

遗传变异 - - 基因重组(交叉)与基因突变。

应该说这两个步骤就是使得子代不同于父代的根本原因(注意,我没有说是子代优于父代,只有经过自然的选择后,才会出现子代优于父代的倾向。)对于这两种遗传操作,二进制编码和浮点型编码在处理上有很大的差异,其中二进制编码的遗传操作过程,比较类似于自然界里面的过程,下面将分开讲述。

1.基因重组/交叉(recombination/crossover)

  • 二进制编码
    二进制编码的基因交换过程非常类似高中生物中所讲的同源染色体的联会过程――随机把其中几个位于同一位置的编码进行交换,产生新的个体。


  • 浮点数编码
    如果一条基因中含有多个浮点数编码,那么也可以用跟上面类似的方法进行基因交叉,不同的是进行交叉的基本单位不是二进制码,而是浮点数。而如果对于单个浮点数的基因交叉,就有其它不同的重组方式了,比如中间重组:随机产生就能得到介于父代基因编码值和母代基因编码值之间的值作为子代基因编码的值。比如5.5和6交叉,产生5.7,5.6。

2.基因突变(Mutation)

  • 二进制编码
    基因突变过程:基因突变是染色体的某一个位点上基因的改变。基因突变使一个基因变成它的等位基因,并且通常会引起一定的表现型变化。正如上面所说,二进制编码的遗传操作过程和生物学中的过程非常相类似,基因串上的“ 0”或“ 1”有一定几率变成与之相反的“ 1”或“ 0”。例如下面这串二进制编码:

101101001011001

经过基因突变后,可能变成以下这串新的编码:

001101011011001

  • 浮点型编码
    浮点型编码的基因突变过程一般是对原来的浮点数增加或者减少一个小随机数。比如原来的浮点数串如下:

1.2,3.4,5.1, 6.0, 4.5

变异后,可能得到如下的浮点数串:

1.3,3.1,4.9, 6.3, 4.4

当然,这个小随机数也有大小之分,我们一般管它叫“步长”。(想想“袋鼠跳”问题,袋鼠跳的长短就是这个步长。)一般来说步长越大,开始时进化的速度会比较快,但是后来比较难收敛到精确的点上。而小步长却能较精确的收敛到一个点上。所以很多时候为了加快遗传算法的进化速度,而又能保证后期能够比较精确地收敛到最优解上面,会采取动态改变步长的方法。其实这个过程与前面介绍的模拟退火过程比较相类似。

到此为止,基因编码,基因适应度评估,基因选择,基因变异都一一实现了,剩下来的就是把这些遗传过程的“零件”装配起来了。(写成代码)

下面是上例的运行结果:





红点代表真实的最大点,由求导法可求的为f(1.85)=3.85

C 语言的实现

以前搞数模时懂得非常浅,只会copy别人的代码(一般是MATLAB),改一改值和参数,东拼西凑就拿过来用了,根本没有搞懂的其内在的原理到底是什么,太急功近利了,最近在学习数据结构与算法,正好可以回顾下,这里放上一个大佬写的代码,采用的是浮点数编码。能学到很多,希望以后有时间我也能自己写。

以遗传算法常见的一个应用领域——求解复杂的非线性函数极值问题为例,来说明如何用代码来具体实现上述的遗传算法操作。求解的函数来自《MATLAB智能算法30个案例分析》,即:

f = -5sin(x1)sin(x2)sin(x3)sin(x4)sin(x5) - sin(5x1)sin(5x2)sin(5x3)sin(5x4)sin(5x5) + 8

其中x1,x2,x3,x4,x5是0~0.9*PI之间的实数。该函数的最小值为2,当x1,x2,x3,x4,x5都取PI/2时得到。

使用C语言实现的遗传算法求解代码如下:

  1/*  2 * 遗传算法求解函数的最优值问题  3 * 参考自《MATLAB智能算法30个案例分析》  4 * 本例的寻优函数为:f = -5*sin(x1)*sin(x2)*sin(x3)*sin(x4)*sin(x5) - sin(5*x1)*sin(5*x2)*sin(5*x3)*sin(5*x4)*sin(5*x5) + 8  5 * 其中x1,x2,x3,x4,x5是0~0.9*PI之间的实数。该函数的最小值为2,当x1,x2,x3,x4,x5都取PI/2时得到。  6 * update in 16/12/3  7 * author:Lyrichu  8 * email:919987476@qq.com  9 */ 10#include 11#include 12#include 13#include 14#define PI 3.1415926 //圆周率 15#define sizepop 50 // 种群数目 16#define maxgen 500 // 进化代数 17#define pcross 0.6 // 交叉概率 18#define pmutation 0.1 // 变异概率 19#define lenchrom 5 // 染色体长度 20#define bound_down 0 // 变量下界,这里因为都相同,所以就用单个值去代替了,如果每个变量上下界不同,也许需要用数组定义 21#define bound_up (0.9*3.1415926) // 上界 22double chrom[sizepop][lenchrom]; // 种群数组 23double fitness[sizepop]; //种群每个个体的适应度 24double fitness_prob[sizepop]; // 每个个体被选中的概率(使用轮盘赌法确定) 25double bestfitness[maxgen]; // 每一代最优值 26double gbest_pos[lenchrom]; // 取最优值的位置 27double average_best[maxgen+1]; // 每一代平均最优值 28double gbest; // 所有进化中的最优值 29int gbest_index; // 取得最优值的迭代次数序号 30 31 32// 目标函数 33double fit_func(double * arr) 34{ 35    double x1 = *arr; 36    double x2 = *(arr+1); 37    double x3 = *(arr+2); 38    double x4 = *(arr+3); 39    double x5 = *(arr+4); 40    double func_result = -5*sin(x1)*sin(x2)*sin(x3)*sin(x4)*sin(x5) - sin(5*x1)*sin(5*x2)*sin(5*x3)*sin(5*x4)*sin(5*x5) + 8; 41    return func_result; 42} 43 44// 求和函数 45double sum(double * fitness) 46{ 47    double sum_fit = 0; 48    for(int i=0;i 49        sum_fit += *(fitness+i); 50    return sum_fit; 51} 52// 求最小值函数 53double * min(double * fitness) 54{ 55    double min_fit = *fitness; 56    double min_index = 0; 57    static double arr[2]; 58    for(int i=1;i 59    { 60        if(*(fitness+i)  61        { 62            min_fit = *(fitness+i); 63            min_index = i; 64        } 65    } 66    arr[0] = min_index; 67    arr[1] = min_fit; 68    return arr; 69} 70// 种群初始化 71void init_chrom() 72{ 73    for(int i=0;i 74    { 75        for(int j=0;j 76        { 77            chrom[i][j] = bound_up*(((double)rand())/RAND_MAX); 78        } 79        fitness[i] = fit_func(chrom[i]); // 初始化适应度 80    } 81} 82// 选择操作 83void Select(double chrom[sizepop][lenchrom]) 84{ 85    int index[sizepop]; 86    for(int i=0;i 87    { 88        double * arr = chrom[i]; 89        fitness[i] = 1/(fit_func(arr)); // 本例是求最小值,适应度为目标函数的倒数,即函数值越小,适应度越大 90    } 91    double sum_fitness = 0; 92    for(int i=0;i 93    { 94        sum_fitness += fitness[i]; // 适应度求和 95    } 96    for(int i=0;i 97    { 98        fitness_prob[i] = fitness[i]/sum_fitness; 99    }100    for(int i=0;i101    {102        fitness[i] = 1/fitness[i]; // 恢复函数值103    }104    for(int i=0;i// sizepop 次轮盘赌105    {106        double pick = ((double)rand())/RAND_MAX;107        while(pick 0.0001)108            pick = ((double)rand())/RAND_MAX;109        for(int j=0;j110        {111            pick = pick - fitness_prob[j];112            if(pick<=0)113            {114                index[i] = j;115                break;116            }117118        }119120    }121    for(int i=0;i122    {123        for(int j=0;j124        {125            chrom[i][j] = chrom[index[i]][j];126        }127        fitness[i] = fitness[index[i]];128    }129}130131// 交叉操作132void Cross(double chrom[sizepop][lenchrom])133{134    for(int i=0;i135    {136        // 随机选择两个染色体进行交叉137        double pick1 = ((double)rand())/RAND_MAX;138        double pick2 = ((double)rand())/RAND_MAX;139        int choice1 = (int)(pick1*sizepop);// 第一个随机选取的染色体序号140        int choice2 = (int)(pick2*sizepop);// 第二个染色体序号141        while(choice1 > sizepop-1)142        {143            pick1 = ((double)rand())/RAND_MAX;144            choice1 = (int)(pick1*sizepop); //防止选取位置过界145        }146        while(choice2 > sizepop-1)147        {148            pick2 = ((double)rand())/RAND_MAX;149            choice2 = (int)(pick2*sizepop);150        }151        double pick = ((double)rand())/RAND_MAX;// 用于判断是否进行交叉操作152        if(pick>pcross)153            continue;154        int flag = 0; // 判断交叉是否有效的标记155        while(flag == 0)156        {157            double pick = ((double)rand())/RAND_MAX;158            int pos = (int)(pick*lenchrom);159            while(pos > lenchrom-1)160            {161                double pick = ((double)rand())/RAND_MAX;162                int pos = (int)(pick*lenchrom); // 处理越界163            }164            // 交叉开始165            double r = ((double)rand())/RAND_MAX;166            double v1 = chrom[choice1][pos];167            double v2 = chrom[choice2][pos];168            chrom[choice1][pos] = r*v2 + (1-r)*v1;169            chrom[choice2][pos] = r*v1 + (1-r)*v2; // 交叉结束170            if(chrom[choice1][pos] >=bound_down && chrom[choice1][pos]<=bound_up && chrom[choice2][pos] >=bound_down && chrom[choice2][pos]<=bound_up)171                flag = 1; // 交叉有效,退出交叉,否则继续下一次交叉172173        }174175    }176}177178// 变异操作179void Mutation(double chrom[sizepop][lenchrom])180{181    for(int i=0;i182    {183        double pick = ((double)rand())/RAND_MAX; // 随机选择一个染色体进行变异184        int choice = (int)(pick*sizepop);185        while(choice > sizepop-1)186        {187            pick = ((double)rand())/RAND_MAX;188            int choice = (int)(pick*sizepop);  // 处理下标越界189        }190        pick = ((double)rand())/RAND_MAX; // 用于决定该轮是否进行变异191        if(pick>pmutation)192            continue;193        pick = ((double)rand())/RAND_MAX;194        int pos = (int)(pick*lenchrom);195        while(pos > lenchrom-1)196        {197            pick = ((double)rand())/RAND_MAX;198            pos = (int)(pick*lenchrom);199        }200        double v = chrom[i][pos];201        double v1 = v - bound_up;202        double v2 = bound_down - v;203        double r = ((double)rand())/RAND_MAX;204        double r1 = ((double)rand())/RAND_MAX;205        if(r >= 0.5)206            chrom[i][pos] = v - v1*r1*(1-((double)i)/maxgen)*(1-((double)i)/maxgen);207        else208            chrom[i][pos] = v + v2*r1*(1-((double)i)/maxgen)*(1-((double)i)/maxgen); // 注意这里写法是不会越界的,所以只用一次变异就可以了209    }210}211212// 主函数213int main(void)214{215    time_t start,finish;216    start = clock(); // 程序开始计时217    srand((unsigned)time(NULL)); // 初始化随机数种子218    init_chrom(); // 初始化种群219    double * best_fit_index = min(fitness);220    int best_index =(int)(*best_fit_index);221    gbest = *(best_fit_index+1); // 最优值222    gbest_index = 0;223    average_best[0] = sum(fitness)/sizepop; //初始平均最优值224    for(int i=0;i225        gbest_pos[i] = chrom[best_index][i];226    // 进化开始227    for(int i=0;i228    {229        Select(chrom); // 选择230        Cross(chrom); //交叉231        Mutation(chrom); //变异232        for(int j=0;j233        {234            fitness[j] = fit_func(chrom[j]);235        }236        double sum_fit = sum(fitness);237        average_best[i+1] = sum_fit/sizepop; // 平均最优值238        double * arr = min(fitness);239        double new_best = *(arr+1);240        int new_best_index = (int)(*arr); // 新最优值序号241        if(new_best 242        {243            gbest = new_best;244            for(int j=0;j245            {246                gbest_pos[j] = chrom[new_best_index][j];247            }248            gbest_index = i+1;249        }250251    }252    finish = clock(); // 程序计算结束253    double duration = ((double)(finish-start))/CLOCKS_PER_SEC;254    printf("程序计算耗时:%lf秒\n.",duration);255    printf("遗传算法进化了%d次,最优值为:%lf,最优值在第%d代取得,此代的平均最优值为%lf.\n",maxgen,gbest,gbest_index,average_best[gbest_index]);256    printf("取得最优值的地方为(%lf,%lf,%lf,%lf,%lf).\n",gbest_pos[0],gbest_pos[1],gbest_pos[2],gbest_pos[3],gbest_pos[4]);257    return 0;258}

说明:

(1)这里是求函数的最小值,函数值越小的个体适应度越大,所以这里采用倒数变换,即把适应度函数F(x)定义为1/f(x)(f(x)为目标函数值),这样就保证了适应度函数F(x)越大,个体是越优的。(当然我们已经保证了f(x)始终是一个正数)

(2) 选择操作采用的是经典的轮盘赌法,即每个个体被选为父代的概率与其适应度是成正比的,适应度越高,被选中的概率越大,设pi为第i个个体被选中的概率,则

pi=Fi/(∑Fi),Fi为第i个个体的适应度函数。

(3)交叉操作:由于个体采用实数编码,所以交叉操作也采用实数交叉法,第k个染色体ak和第l个染色体al在第j位的交叉操作方法为:

akj=aij(1-b)+aijb;  alj=alj(1-b)+akjb,其中b是[0,1]之间的随机数。k,l,j都是随机产生的,即随机选取两个父代,再随机选取一个基因,按照公式完成交叉操作。

(4)变异操作:第i个个体的第j个基因进行变异的操作方法是:

r>=0.5时,aij=aij+(amax-aij)*f(g)

r<0.5时,aij=aij+(amin-aij)*f(g)

其中amax是基因aij的上界,amin是aij的下界,f(g)=r2*(1-g/Gmax)2,r2是一个[0,1]之间的随机数,g是当前迭代次数,Gmax是进化最大次数,r是[0,1]之间的随机数。

可以看出,当r>=0.5时,aij是在aij到amax之间变化的;r<0.5时,aij是在amin到aij之间变化的。这样的函数其实可以构造不止一种,来完成变异的操作。只要符合随机性,以及保证基因在有效的变化范围内即可。

我在ubuntu16.04下,使用gcc编译器运行得到的结果如下:


从结果上来看,种群数目为50,进化代数为500时,得到的最优解为2.014102已经非常接近实际的最优解2了,当然,如果把种群数目再变大比如500,进化代数变多比如1000代,那么得到的结果将会更精确,种群数目为500,进化代数为1000时的最优解如下:


可以看出,增加种群数目以及增加进化代数之后,最优值从2.014102变为2.000189,比原来的精度提高了两位,但是相应地,程序的运行时间也显著增加了(从0.08秒左右增加到2秒左右)。所以在具体求解复杂问题的时候(比如有的时候会碰到函数参数有几十个之多),我们就必须综合考虑最优解的精度以及运算复杂度(运算时间)这两个方面,权衡之后,取合适的参数进行问题的求解。欲了解更详细的信息,请点击『阅读原文

参考文献

  1. https://blog.csdn.net/u010451580/article/details/51178225

  2. https://blog.csdn.net/hiudawn/article/details/80144221

  3. https://www.cnblogs.com/lyrichu/p/6152897.html

循环首次适应算法_遗传算法 | 了解底层, 摆脱工具箱相关推荐

  1. 循环首次适应算法_面向6G的极化编码链路自适应技术

    点击上方蓝色字体,关注我们 相关链接: "6G专刊"重磅推出 / 2020年第6期目录来啦 6G愿景及潜在关键技术分析 6G愿景与需求:数字孪生.智能泛在 毕奇 / 移动通信的主要 ...

  2. 循环首次适应算法_数据结构与算法之2——排序问题

    排序真的是数据结构与算法中的重中之重啊,无论是对编程能力的提升,以后工作后的应用,或者是应对面试的时候都是经常需要用到的.基本的几个经典排序一定要做到滚瓜烂熟,能够做到给你一个具体的排序算法题,一定能 ...

  3. 用循环首次适应算法、最佳适应算法和最坏适应算法,模拟可变分区存储管理,实现对内存区的分配和回收管理

    湖南师范大学 信息科学与工程学院 操作系统课程实验报告 实验项目名称: 可变分区存储管理 题目 实验一.可变分区存储管理 一.实验目的: 加深对可变分区存储管理的理解: 提高用C语言编制大型系统程序的 ...

  4. 【操作系统】-- 动态分区分配算法(首次适应算法FF、最佳适应算法BF、最坏适应算法WF、循环首次适应算法NF)

    一.首次适应算法 1.算法思想 每次从低地址开始查找,找到第一个能满足大小的空闲分区. 2.如何实现 空闲分区以地址递增的次序排列,每次分配内存时顺序查找空闲分区链,找到大小能满足要求的第一个空闲分区 ...

  5. 动态分区分配的“首次适应算法_无人机集群——航迹规划你不知道的各种算法优缺点...

    我们都知道无人机(UAV )因具有可探测性低.造价低廉.不惧伤亡.起降简单.操作灵活.系统配置多样化.自动控制智能化等特点,因而在未来一体化联合作战中扮演越来越重要的角色.然而早期的无人机都是按照地面 ...

  6. 旅行商问题(动态规划_爬山算法_遗传算法)

    问题描述 ​ 旅行商问题(Travelling Salesman Problem, 简记TSP,亦称货郎担问题):设有n个城市和距离矩阵D=[dij],其中dij表示城市i到城市j的距离,i,j=1, ...

  7. java利用复循环洗牌算法_随机洗牌算法

    今天偶然看到群里的朋友说道,面试被问如何将扑克牌随机洗牌输出.笔者觉得这道题挺有意思而且挺开放性,有多种不同的实现方式.然后我就随手写了一个算法,仔细一想这个算法的优化空间挺大,于是又写出三种算法. ...

  8. 进化算法_遗传算法相关资料

    Hyperopt中文文档导读:自动调参神器 Python 启发式算法的包 如何通俗易懂地解释遗传算法 一文读懂遗传算法工作原理(附 Python 实现) 遗传算法(python版) 遗传算法与其pyt ...

  9. 动态分区分配的“首次适应算法_动态图划分复制算法:Leopard

    数据管理和系统实现课程上要分享的论文:<LEOPARD: Lightweight Edge-Oriented Partitioning and Replication for Dynamic G ...

最新文章

  1. 给网站增加https的简单方法
  2. 课时 16 深入理解 etcd:基于原理解析(曾凡松)
  3. linux使用技巧:自动补全、常用快捷键* ? [] {}
  4. SWF反编译神器ASV2013功能展示(下)
  5. 业务异常 java_谈谈RxJava处理业务异常的几种方式
  6. linux进入mongodb数据库命令,MongoDB 常用数据库命令,命令是如何工作的这里
  7. 浅谈C中的malloc和free\\感谢lj_860603,工作了,没时间验证了,觉得挺好的,转载了...
  8. DDL DML DCL 简介
  9. SylixOS 内存管理源代码分析--vmmMalloc.c
  10. 博弈论基础知识--非合作博弈,零和博弈,负和博弈,主从博弈,Nash均衡
  11. Java常见面试题汇总-----------JVM专题(Java类加载机制)
  12. CentOS 7安装MinDoc文档系统
  13. OK6410A 之 SPI 驱动 测试 步骤
  14. 台式win10电脑耳机没声音
  15. Auto.js逆向分析-提取脚本文件(附源码)
  16. 小鸡G4工程款 上手体验
  17. 解决IBM Lenovo x3650M5不开机报错system board fault
  18. sakai mysql_Sakai中使用MySQL数据库
  19. Word高效指南 - 自动更正功能
  20. 短租民宿平台小程序系统

热门文章

  1. 开源机器学习工具SQLFlow
  2. 关于抢红包的_关于抢红包现象的材料作文
  3. clob和blob是不是可以进行模糊查询_SQL简单查询语、运算符学习和练习
  4. eclipse-中如何显示工程树形结构
  5. php测试宽带速度慢,性能测试问题排查一例——网络带宽瓶颈
  6. java记秒,Java程序获取自Java时代开始以来的秒数
  7. C语言scanf输入a3,【C语言】04 printf和scanf函数
  8. collectors 求和_Java-Collectors常用的20个方法
  9. 美国《国家地理》:年度十大新闻
  10. Python中用format函数格式化字符串的用法(2.7版本讲解哦!)