第五篇为《遗传算法的基础知识与编程》
一、遗传算法基础知识
借鉴自然进化的理念,优化问题的过程可以看成类似于生物进化的过程,通过模拟自然界的生物进化,遗传算法被提出用于解决优化问题。
遗传算法吸收了生命科学与工程科学的重要理论成果,用于解决复杂优化问题。
自然进化过程如下图1所示,其主要包括自然选择,交配、变异以及淘汰了这些过程,遗传算法结合自然进化的方式的方式来解决优化问题。
图1 自然进化过程
在生物的进化与遗传过程中有一个重要的携带信息的结构,即染色体。染色体是遗传信息基因的载体,基因在染色体上按照一定的次序组合。
在遗传算法中,问题的每个有效解也被称为一个“染色体”,染色体的具体形式是一个使用特定编码方式生成的编码串。编码串中的每一个编码单元称为“基因”。
在遗传算法中海油以下基本概念:
(1)适应值:适应值用于区分染色体的优劣,适应值越大的染色体越优秀。
(2)评估函数:用来计算并确定染色体对应的适应值
(3)选择算子:按照一定的规则对群体的染色体进行选择,得到父代种群。越优秀的染色体被选中的次数越多。
(4)交配算子:作用于没两个成功交配的染色体,染色体交换各自的部分基因,产生两个子染色体。子染色体取代父染色体进入新种群,而没有交配的染色体则直接进入新种群。
(5)变异算子:使新种群进行小概率的变异。
二、遗传算法执行步骤
(一)进一步概念
1、染色体编码
在使用遗传算法的时候,首先需要把要解决的问题的解表示出来,即染色体的编码方式。只有先确定了编码方式,才能进行接下类的交配变异等操作。
目前常用的比较简单的编码方式有:
(1)二进制编码方法:编码的染色体是一个二进制符号序列,在下面的程序编程中使用的编码方法就是二进制编码方法。在二进制编码方法中,染色体上的每个基因智能取0和1,就是对应着下面0-1背包的0和1.
(2)浮点数编码方式:浮点数编码方法中,染色体的长度等于问题定义的解的变量个数,染色体的每一个基因等于解的每一维变量。
2、群体初始化
遗传算法在开始进化迭代之前需要初始化进化的群体,一般采用产生随机数的方法进行群体的初始化。比如下面的编程就使用随机数初始这个染色体,因为染色体上只有0与1,因此只需根据随机数来生成0与1即可。
3、评估适应值
对于适应值的评估使用评估函数来进行,适应度用来评估一个染色体的优劣。评估函数基本是根据问题的优化目标来进行确定的。在遗传算法中,规定适应值越大的染色体越好。
4、选择算子
种群的选择操作使用轮盘赌选择算法,轮盘赌选择算法基于概率的随机选择,比如电影里经常看到的在枪里放一颗子弹,进行开枪赌运气。
5、交配算子
在染色体交配阶段,每个染色体能否进行交配由交配概率P来决定,如果随机数在这个概率之内则表示两个染色体需要进行交配。
6、变异算子
同样的有一个变异算子,设定一个变异概率P,产生随机数小于这个编译概率,那么可以进行变异。
(二)遗传算法步骤
Step 1 :初始化群体N,使用随机数生成器对群体中的每个个体染色体进行随机编码,标记迭代次数c=0。
Step 2:使用评估函数对这些初始出的个体染色体进行评估,计算出他们的适应度,然后标记最大的适应值Best。
Step 3:采用轮盘赌方式进行选择操作,产生规模同样为N的种群
Step 4:按照交配概率P,选择则染色体进行交配。
Step 5:按照变异概率P,选择染色体进行变异。
Step 6:使用交配变异后的新种群代替旧种群,重新通过评估函数计算适应值。若新种群的最大适应值在于前面标记的Best,就更新Best。
Step 7:将迭代次数加1,继续第三步进行迭代。
三、遗传算法编程
代码好像不是自己写的,来自网络,三年前找到的代码,找不到出处了,侵删。
该代码为遗传算法解决0-1背包问题,主要包含如下两个文件:
(1)ga.c:
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#include <string.h>
#define ROUND 4000
#define INIT 100 //初始染色体数目
#define TotalGoods 50 //货物总数
#define Load 600 //背包总容量
int Weights[TotalGoods]; //存储每件货物重量数组
int Values[TotalGoods]; //存储每件货物价值数组
int TotalWeights[INIT]; //存储每个组合的总重量
int TotalValues[INIT]; //存储每个组合的总价值
double Fitness[INIT]; //适应度
char ParentChromosome[INIT][TotalGoods+1]; //存储父代基因组合,即染色体
char ChildChromosome[INIT][TotalGoods+1]; //存储子代基因组合,即染色体
double Prob[INIT]; //每个组合的选择概率,用于轮盘赌选择
int count = 0;
/*******************************************************/
//读取配置文件
//Line1: 货物重量
//Line2: 货物价值
//Line3……:备选初始组合,因为初始组合是随机产生的,不能保证满足“重量和<背包容量”,须在本程序中进行判断
/******************************************************/
void ReadConfFIle(char *filename)
{
int i, j;
int sum=0;
char *fname = filename;
FILE *fid = fopen(fname, "r");
for (i=0; i< TotalGoods; i++)
{
fscanf(fid, "%d",&Weights[i]);
}
for (i=0; i< TotalGoods; i++)
{
fscanf(fid, "%d", &Values[i]);
}
//从文件中读取满足约束条件的组合作为初始组合
for (i=0; i< INIT; i++)
{
fscanf(fid, "%s", &ParentChromosome[i]);
sum=0;
for (j=0; j< TotalGoods; j++)
{
sum += (int)(ParentChromosome[i][j]-48) * Weights[j];
}
if(sum >= Load)
{
i--;
}
}
fclose(fid);
}
/*******************************************/
//计算所有个体的适应度
//得到适应度数组
//输入:
// char Chromosome[][]: 父辈基因的组合
//输出:
// double *fitness : 所有基因组合的适应度
/******************************************/
void CalculateFitness(char Chromosome[][TotalGoods+1],double *fitness )
{
int i,j;
for (i=0; i< INIT; i++)
{
TotalValues[i] = 0;
TotalWeights[i]=0;
for (j=0; j< TotalGoods; j++)
{
TotalWeights[i]+=Weights[j]*(int)(Chromosome[i][j]-48);
TotalValues[i] +=Values[j] *(int)(Chromosome[i][j]-48);
}
fitness[i]=(double)TotalValues[i];
}
}
/********************************************************/
//通过轮盘赌来选择确定选择交叉的父染色体
//parent中存储了两个选择的父染色体的下标
//输入:
// double *fitness: 适应度,即每一个组合的价值
// double *prob : 每个组合的选择概率
//输出:
// int *parent : 选择出来的两个父染色体的标号
/*************************************