基于遗传算法求解多项式最值

本文介绍使用遗传算法求解特定多项式在指定区间内的最小值问题,详细讲解了算法的执行过程,包括编码、交叉、变异、选择等关键步骤,并提供完整代码实现。

1 系统分析与初步设计

1.1 遗传算法的执行过程

遗传算法是从代表问题可能潜在的解集的一个种群(population)开始的,而一个种群则由经过基因(gene)编码的一定数目的个体(individual)组成。每个个体实际上是染色体(chromosome)带有特征的实体。
染色体作为遗传物质的主要载体,即多个基因的集合,其内部表现(即基因型)是某种基因组合,它决定了个体的形状的外部表现,如黑头发的特征是由染色体中控制这一特征的某种基因组合决定的。因此,在一开始需要实现从表现型到基因型的映射即编码工作。由于仿照基因编码的工作很复杂,我们往往进行简化,如二进制编码。
初代种群产生之后,按照适者生存和优胜劣汰的原理,逐代(generation)演化产生出越来越好的近似解,在每一代,根据问题域中个体的适应度(fitness)大小选择(selection)个体,并借助于自然遗传学的遗传算子(genetic operators)进行组合交叉(crossover)和变异(mutation),产生出代表新的解集的种群。
这个过程将导致种群像自然进化一样的后生代种群比前代更加适应于环境,末代种群中的最优个体经过解码(decoding),可以作为问题近似最优解。

在这里插入图片描述

1.2 问题引出与分析

问题:
运用遗传算法对多项式f(x)=x*(x*(x*(x*(x*(x-10)-26)+344)+193)-1846)-1680求最小值,要求在(-8,8)间寻找使表达式达到最小的x,误差为0.001
问题分析:
题目要求误差不超过0.001,即对于x可以每0.001取一点,总的求解空间为:
N = (8 - (-8)) * 1000 = 16000

1.3 大致实现过程

遗传算法中每一条染色体,对应着遗传算法的一个解决方案,一般我们用适应性函数(fitness function)来衡量这个解决方案的优劣。所以从一个基因组到其解的适应度形成一个映射。遗传算法的实现过程实际上就像自然界的进化过程那样。

  1. 随机产生种群。
  2. 根据策略判断个体的适应度,是否符合优化准则,若符合,输出最佳个体及其最优解,结束。否则,进行下一步。
  3. 依据适应度选择父母,适应度高的个体被选中的概率高,适应度低的个体被淘汰。
  4. 用父母的染色体按照一定的方法进行交叉,生成子代。
  5. 对子代染色体进行变异。
  6. 由交叉和变异产生新一代种群,返回步骤2,直到最优解产生。
    遗传算法并不保证你能获得问题的最优解,但是使用遗传算法的最大优点在于你不必去了解和操心如何去“找”最优解。而只要简单的“否定”一些表现不好的个体就行了。

2 具体实现细节

2.1 编码方法

编码是应用遗传算法时要解决的首要问题,也是设计遗传算法时的一个关键步骤。编码方法影响到交叉算子、变异算子等遗传算子的运算方法,很大程度上决定了遗传进化的效率。
目前编码方法总共有三大类:二进制编码法、浮点编码法、符号编码法。

2.1.1二进制编码法
二进制编码法只使用0和1两种数字, 代表基因的两种碱基,若干个0,1组合起来就是一条染色体。它有以下一些优点:
编码、解码操作简单易行
交叉、变异等遗传操作便于实现

2.1.2浮点编码法
浮点法,是指个体的每个基因值用某一范围内的一个浮点数来表示。在浮点数编码方法中,必须保证基因值在给定的区间限制范围内,遗传算法中所使用的交叉、变异等遗传算子也必须保证其运算结果所产生的新个体的基因值也在这个区间限制范围内。它有下面几个优点:
适用于在遗传算法中表示范围较大的数
适用于精度要求较高的遗传算法
便于较大空间的遗传搜索
改善了遗传算法的计算复杂性,提高了运算交率

2.1.3 符号编码法
符号编码法是指个体染色体编码串中的基因值取自一个无数值含义、而只有代码含义的符号集如{A,B,C…}。
符号编码的主要优点是:
符合有意义积术块编码原则
便于在遗传算法中利用所求解问题的专门知识
便于遗传算法与相关近似算法之间的混合使用。

因为多项式求解不算一个复杂的问题,二进制表示法足以使用。因此为了编码的简洁性考虑,最终采取了二进制编码。
对于问题分析中算出的求解空间为16000,2^13 < 16000 < 2^14,因此可以用14位二进制来表示。

2.2 终止规则

  1. 最优解在连续的20次循环中改变量小于0.00001,此事认为这个最优解为满足题目要求的最优解,求解成功,退出程序
  2. 总的循环次数大于400次时,循环也将结束,这种情况按照求解失败处理

2.3 其它运行参数

采用一般遗传算法参数,如下:
SUM:种群数目,20;
MAXloop:遗传算法的迭代次数,一般取400;
crossp:交叉概率,0.7;
mp:变异概率,0.04。

2.4 交叉算子

遗传算法的交叉操作,是指对两个相互配对的染色体按某种方式相互交换其部分基因,从而形成两个新的个体。
本文采用最常用的单点交叉法,代码如下:

if(randsign(crossp) == 1)		//按照crossp的概率对选择的染色体进行交叉操作 
{
	mask1 = createmask(randbit(0 , 14));		//由ranbit选择交叉位 
	mask2 = ~mask1;				//形成一个类似000111之类的二进制码编码 
	gen_new[i].info = ((gen_group[i].info) & mask2) + ((gen_group[j].info) & mask1);
	x = convertionB2D(gen_new[i].info);
	gen_new[i].suitability = x*(x*(x*(x*(x*(x-10)-26)+344)+193)-1846)-1680;
}
else 		//不进行交叉 
{
	gen_new[i].info = gen_group[i].info;
	gen_new[i].suitability = gen_group[i].suitability;
}

2.5 变异规则

遗传算法中的变异运算,是指将个体染色体编码串中的某些基因座上的基因值用该基因座上的其它等位基因来替换,从而形成新的个体。
本文采用基本位变异法,首先确认变异多少位,其次确认变异第几位,然后对相应位做取反操作,代码如下:

if(randsign(mp) == 1)					//按照变异概率进行变异操作
{
	double x;
	int i , j = randbit(0 , 14);		//确定变异多少位
	for(i = 0 ; i < j ; i++)
	{
		int m , j = randbit(0 , 14);	//确定变异第几位
		m = 1 << j;
		gen_group[i].info = gen_group[i].info ^ m;
	}
	x = convertionB2D(gen_group[i].info);
	gen_group[i].suitability = TestFunction;
}

2.6 选择函数

在进行交叉、变异后,种群中的个体数达到2SUM个,将这2SUM个染色体按其适应度进行排序,根据贪心算法保留SUM个淘汰其他的,使种群在整体上得到进化,代码如下:

for(i = 0; i < SUM; i++)
{
	if(gen_group[i].suitability > gen_new[i].suitability)
	{
		gen_group[i].info = gen_new[i].info;
		gen_group[i].suitability = gen_new[i].suitability;
	}
}

3 完整代码

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
 
#define SUM 20          	//种群数目
#define MAXloop 400       	//最大循环次数
#define error 0.00001       //若两次最优值之差小于此数则认为结果没有改变
#define crossp 0.7        	//交叉概率
#define mp 0.04           	//变异概率
#define TestFunction x*(x*(x*(x*(x*(x-10)-26)+344)+193)-1846)-1680
 
 
//用于求解函数y=x^6-10x^5-26x^4+344x^3+193x^2-1846x-1680在(-8,8)之间的最小值 
 
typedef struct              //定义染色体结构
{
	int info;        		//染色体结构,用一整型数的后14位作为染色体编码 
	double suitability;		//次染色体所对应的适应度函数值,在本题中为表达式的值 
} gen;
gen gen_group[SUM];			//定义父种群
gen gen_new[SUM];  		//定义交叉产生的子种群
 
gen gen_result;    			//记录最优的染色体
int result_unchange_time; 	//记录在error前提下最优值为改变的循环次数
 
 
/**************函数声明******************/
/***********主函数调用函数***************/ 
void  initiate();          	//初始化函数,主要负责产生初始化种群 
void  evaluation();			//评估种群中各染色体的适应度,并据此进行排序
int   record();				//记录每次循环产生的最优解并判断是否终止循环  
void  cross();				//交叉函数 
void  mutation();			//变异函数
void  selection();			//选择函数 
gen   showresult(int);		//显示结果
 
/**************其他函数******************/
int    randsign(double p);		  	//按照概率p产生随机数0、1,其值为1的概率为p 
int    randbit(int i,int j);		//产生一个在i,j两个数之间的随机整数 
int    randnum();					//随机产生一个由14个基因组成的染色体 
int    convertionD2B(double x);		//对现实解空间的可能解x进行二进制编码(染色体形式) 
double convertionB2D(int x);		//将二进制编码x转化为现实解空间的值 
int    createmask(int a);			//用于交叉操作
void   QuickSort(gen* a, int low, int high);	//排序算法 
 
int main()
{
	int i, run_time = 0;
	gen temp, z;
	temp.suitability = 1000;
	z.suitability = 1000;
	srand((unsigned)time(NULL));

	for(i = 0; i < 10; i++)
	{
		int i , flag = 0;
	
		initiate();				//产生初始化种群 
		for( i = 0 ; i < MAXloop ; i++ )
		{
			evaluation();	//对种群进行评估、排序 
			if( record() == 1 )	//满足终止规则1,则flag=1并停止循环 
			{
				flag = 1;
				break;
			}
			
			cross();			//进行交叉操作 
			mutation();			//变异操作 
			selection();		//对父子种群中选择最优的NUM个作为新的父种群 
			run_time++;
		}
		z = showresult( flag );	//按照flag显示寻优结果
		if(temp.suitability > z.suitability)
			temp = z;
	}

	printf("当取值x = %f时, 表达式y=x^6-10x^5-26x^4+344x^3+193x^2-1846x-1680达到最小值:\ny = %f\n",convertionB2D(temp.info), temp.suitability);

	system("pause");
	
	return 0;
}
 
void initiate()
{
	int i;
	for( i = 0 ; i < SUM ; i++ )
	{
		double x;
		gen_group[i].info = randnum();		//调用randnum()函数建立初始种群	
		x = convertionB2D(gen_group[i].info);
		gen_group[i].suitability = TestFunction; 	//提取公因式比原式更快
	}
		
	gen_result.suitability = 1000;
	result_unchange_time = 0;
}
 
void evaluation()
{
	QuickSort(gen_group, 0, SUM);
}

int record()	//记录最优解和判断是否满足条件 
{
	double x;
 
	x = gen_result.suitability - gen_group[0].suitability;
	if(x < 0)x = -x;
	if(x < error)
	{
		result_unchange_time++;
		if(result_unchange_time >= 20)return 1;
	}
	else
	{
		gen_result.info = gen_group[0].info;
		gen_result.suitability = gen_group[0].suitability;
		result_unchange_time = 0;
	}
	
	return 0;
}
 
void cross()
{
	int i , j;
	int mask1 , mask2;

	for(i = 0 ; i < SUM ; i++)
	{
		double x = 0;
   		j = randbit(i + 1 , SUM - 1);
		if(randsign(crossp) == 1)		//按照crossp的概率对选择的染色体进行交叉操作 
		{
			mask1 = createmask(randbit(0 , 14));		//由ranbit选择交叉位 
			mask2 = ~mask1;				//形成一个类似000111之类的二进制码编码 
			gen_new[i].info = ((gen_group[i].info) & mask2) + ((gen_group[j].info) & mask1);
			x = convertionB2D(gen_new[i].info);
			gen_new[i].suitability = TestFunction;
		}
		else 		//不进行交叉 
		{
			gen_new[i].info = gen_group[i].info;
			gen_new[i].suitability = gen_group[i].suitability;
		}
	}
}

void mutation()
{
	int i, j;
	int gentinfo;
	double gentsuitability;
	for(i = 0 ; i < SUM ; i++)
	{
		if(randsign(mp) == 1)					//按照变异概率进行变异操作
		{
			double x;
			int i , j = randbit(0 , 14);		//确定变异多少位
			for(i = 0 ; i < j ; i++)
			{
				int m , j = randbit(0 , 14);	//确定变异第几位
				m = 1 << j;
				gen_group[i].info = gen_group[i].info ^ m;
			}
			x = convertionB2D(gen_group[i].info);
			gen_group[i].suitability = TestFunction;
		}
	}
}
 
 
void selection()
{
	int i;
	
	QuickSort(gen_group, 0, SUM);
	QuickSort(gen_new, 0, SUM);
	for(i = 0; i < SUM; i++)
	{
		if(gen_group[i].suitability > gen_new[i].suitability)
		{
			gen_group[i].info = gen_new[i].info;
			gen_group[i].suitability = gen_new[i].suitability;
		}
	}
}
 
gen showresult(int flag)//显示搜索结果
{
	FILE* datafd;
	if((datafd = fopen("data.txt" , "a+")) == NULL)
	{
		printf("Cannot create/open file");
		exit(1);
	}
	
	if(flag == 0)
		fprintf(datafd, "已到最大搜索次数,搜索失败!");
	else 
	{
		fprintf(datafd, "当取值x = %f时, 表达式y=x^6-10x^5-26x^4+344x^3+193x^2-1846x-1680达到最小值:\ny = %f\n",convertionB2D(gen_result.info),gen_result.suitability);
	}
	
	return gen_result;
}
 
int randsign(double p)//按概率p返回1
{
	if(rand() > (p * (RAND_MAX + 1)))
		return 0;
	else return 1;
}

int randbit(int i, int j)//产生在i与j之间的一个随机数
{
	int a , l;
	l = j - i + 1;
	a = i + rand() * l / (RAND_MAX + 1);
	return a;
}
int randnum()
{
	int x;
	x = (double)rand() / (RAND_MAX + 1) * 16000;
	return x;
}
double convertionB2D(int x)
{
	double y;
	y = x;
	y = (y - 8000) / 1000;
	return y;
	
}
int convertionD2B(double x)
{
	int g;
	g = (x * 1000) + 8000;
	return g;
}
int createmask(int a)
{
	int mask;
	mask=(1 << (a + 1)) - 1;
	return mask;
}

void QuickSort(gen* a, int low, int high)    
{
 	int i = low, j = high - 1;
	gen temp = a[low];  //取第一个元素为标准数据元素
	while(i < j)
	{
		while((i < j) && (temp.suitability <= a[j].suitability)) 
			j--; //在数组的右端扫描
		if(i < j)
		{
			a[i].info = a[j].info;
			a[i].suitability = a[j].suitability;
			i++;
		}
		while((i < j) && (a[i].suitability < temp.suitability))
			i++; //在数组的左端扫描
		if(i < j)
		{
			a[j].info = a[i].info;
			a[j].suitability = a[i].suitability;
			j--;
		}
	}
	a[i] = temp;
	if(low < i) QuickSort(a, low, i-1); //对左端子集合进行递归
	if(i < high) QuickSort(a, j+1, high);  //对右端子集合进行递归 
}

4 运行结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值