遗传算法(GA)计算二元函数极值(C语言实现、matlab工具箱实现)

本文介绍了使用C语言和MATLAB实现遗传算法(GA)来求解二元函数极值问题的过程。首先,概述了遗传算法的基本原理,并通过流程图进行说明。接着,展示了C语言的实现细节,包括初始化、选择、交叉和变异等步骤。此外,还使用MATLAB验证了C语言实现的结果,并通过绘制函数图像辅助理解。最后,通过GA在MATLAB中的应用进一步确认了C语言实现的正确性。
摘要由CSDN通过智能技术生成

  前段时间尝试了C语言实现PSO算法求计算二元函数极值,这几天稍微空闲下来了又想尝试用另一种比PSO难一点的经典智能优化算法解决这个问题。

  PSO传送门

原理

  遗传算法应用还是非常广泛的。之前打数模的时候经常用它求解公式,或者利用它做一些调度类的问题,例如经典的柔性车间调度问题。但是,在实现的时候更多的是借助matlab和python,有的时候直接拿现成的代码修改参数直接用,对算法的理解还是很不到位的。趁这个机会,打算用C语言实现一次GA算法。

  自制的GA算法流程图如下,和PSO算法一样,个人认为这类算法还是静下心来直接看代码比较好理解,效率更高。

在这里插入图片描述

  整个算法实际上分为两大部分:

  • 第一部分是在为后来的迭代做准备,包括数据编码,初始化等等操作,为第二部分的迭代准备原材料。

  • 第二部分是迭代,实际上就是不断地加工第一部分提供的原材料,直到收敛得到最优结果。

实例

求解极值 C 语言实现

  待求极值函数如下:

  C语言代码如下,主要的流程在 genetic 函数里写的比较清楚;两个题目可以在 fitting 函数里面切换,修改的时候记得改#define里的最大最小值。里面的参数需要根据题目自己调整。

#include<bits/stdc++.h>
using namespace std;

// 染色体数量 也就是种群数量 
#define chromosome_size 300
// 染色体长度 也就是维度 
#define chromosome_length 2
// 进化代数 也就是迭代次数
#define epoch_max 30

// 变量上下界 
#define bound_down 0
#define bound_up 10

// 交叉概率 
#define cross_probability 0.5
// 变异概率 => 模仿生物学,这玩意要低 
#define mutation_probability 0.1


// 染色题本身 二维就是这个解的x,y存储 
double chromosome[chromosome_size][chromosome_length];
// 染色体适应度函数 二维就是这个解的x,y对应的函数的值的存储 
double chromosome_fitness[chromosome_size];
// 个体被选中的概率,这个是遗传算法的特色
double chromosome_chosen[chromosome_size];
// 每次迭代的最优解
double epoch_best_fitness[epoch_max];
 // 每次迭代的最优解的位置,在二维里就是对应的x, y 
double epoch_best[epoch_max];
// 之前所有迭代的最优解的平均值
double average_epoch_best_fitness[epoch_max];
// 存储目前所有迭代中的最优解
double all_best_fitness;
// 存储目前所有迭代中的最优解出现的代数
int all_best_epoch; 

// 求和函数,因为不让用库,只能手写一个 
double sum(double fit[])
{
    double sum_fit = 0;
    for(int i=0;i<chromosome_length;i++)
        sum_fit += fit[i];
    return sum_fit;
}

// 计算适应度的函数,在二维的情况下其实就是计算x, y这俩对应的函数的解 
double fitting(double fit[]){
//	第一题:   
//	double fitness = sin(fit[0])/fit[0]+sin(fit[1])/fit[1];
//	第二题 
	double fitness = (6.452*(fit[0]+0.125*fit[1])*pow((cos(fit[0])-cos(2*fit[1])),2))/sqrt(0.8+pow((fit[0]-4.2),2)+2*pow((fit[1]-7),2))+3.226*fit[1];
	return fitness;
}

// 作用其实就是比较所有散点对应的适应度,输出最大的适应度和对应的位置 
double* max_in_community(double fit[], int size){
	static double result[2];
	result[0] = *fit;
	result[1] = 0;
	for(int i = 0; i < size; i++)
		if(fit[i]>result[0]){
			result[0] = fit[i];
			result[1] = i;
		};
	return result;
}

// 初始化函数,一开始还是随机初始化 
void initialization(void){
	for(int i = 0; i < chromosome_size; i++){
		for(int j = 0; j < chromosome_length; j++)
			// 初始化第一代 
			chromosome[i][j] = bound_up*(((double)rand())/RAND_MAX)+bound_down;
		// 顺便算出来第一批染色体的适应度函数 
		chromosome_fitness[i] = fitting(chromosome[i]);
	}
}

// 选择:求最大值时,适应度越高,被选择的概率越大,经过选择之后,chromosome中大的比例明显提高了 
void select(double chromosome[chromosome_size][chromosome_length]){
	double fitness_sum = 0;
	for(int i=0; i<chromosome_size; i++){
		// 计算适应度 
		chromosome_fitness[i] = fitting(chromosome[i]);
		// 求和,为了之后能算概率 
		fitness_sum += chromosome_fitness[i]; 
	}
	// 计算每个染色体的概率 
	for(int i=0; i<chromosome_size; i++){
		if(i==0)
			chromosome_chosen[i] = chromosome_fitness[i]/fitness_sum; 
		else
			chromosome_chosen[i] = chromosome_fitness[i]/fitness_sum+chromosome_chosen[i-1]; 
	}
	// 选择的主体 => 随机数筛选,染色体的概率越大,越容易被select到 
	int index[chromosome_size]; 
	for(int i=0; i<chromosome_size; i++){
		double rand0 = ((double)rand())/RAND_MAX;
		while(rand0<0.0001)
			rand0 = ((double)rand())/RAND_MAX;
		for(int j=0; j<chromosome_size; j++){
			if(rand0<=chromosome_chosen[j]){
				index[i] = j;
				break;
			}
		}
	}
	// 选择完了,在这里记录
	for(int i=0;i<chromosome_size;i++){
		for(int j=0;j<chromosome_length;j++)
			chromosome[i][j]=chromosome[index[i]][j];
		chromosome_fitness[i] = chromosome_fitness[index[i]];
	}
}

// 交叉
void cross(double chromosome[chromosome_size][chromosome_length]){
	for(int i=0;i<chromosome_size;i++){
		// 上来先判断是否进行交叉操作,大于交叉概率就跳过 
		double pick = ((double)rand())/RAND_MAX;
        if(pick>cross_probability)
            continue;
		// 交叉涉及到两个染色体,在这里随机抽取两个染色体,下面两个是两个染色体的编号 
		int rand1 = (int)((chromosome_size-1)*((double)rand())/RAND_MAX);
		int rand2 = (int)((chromosome_size-1)*((double)rand())/RAND_MAX);
		
		// 交叉主体
		int flag = 0;	// 用来判断后面的循环是否要退出的
		while(!flag){
			// 看染色体的哪部分拿过来交叉互换 
			int where = (int)((chromosome_length-1)*((double)rand())/RAND_MAX);
			double v1 = chromosome[rand1][where];
			double v2 = chromosome[rand2][where];
			// rand是指交叉时采用交叉对象的比例 
			double rand0 = ((double)rand())/RAND_MAX;
			chromosome[rand1][where] = rand0*v2 + (1-rand0)*v1;
			chromosome[rand2][where] = rand0*v2 + (1-rand0)*v2;
			// 判定上下界,在界内就算这次交叉算数,不然重新交叉 
			if(chromosome[rand1][where]<=bound_up&&chromosome[rand1][where]>=bound_down)
				if(chromosome[rand2][where]<=bound_up&&chromosome[rand2][where]>=bound_down)
					flag = 1;
		} 
	} 
}

// 变异 
void mutation(double chromosomes[chromosome_size][chromosome_length]) {
	for(int i=0;i<chromosome_size;i++){
		// 上来先判断是否进行变异操作,大于变异概率就跳过 
		double pick = ((double)rand())/RAND_MAX;
        if(pick>mutation_probability)
        	continue; 
		// 随机抽取染色体的一个位置 
		int where = (int)((chromosome_length-1)*((double)rand())/RAND_MAX);
		
        double v1 = chromosome[i][where] - bound_up;
        double v2 = bound_down - chromosome[i][where];
        
        double r = ((double)rand())/RAND_MAX;
        double r1 = ((double)rand())/RAND_MAX;
        
        if(r>=0.5)
        	chromosome[i][where] = chromosome[i][where] - v1*r1*(1-((double)i)/epoch_max)*(1-((double)i)/epoch_max);
        else
        	chromosome[i][where] = chromosome[i][where] + v1*r1*(1-((double)i)/epoch_max)*(1-((double)i)/epoch_max);
	}
}

// 遗传搜索
void genetic(){
	//————————————Step1————————————//
	//——————————各种初始化——————————// 
	// 初始化染色体 
	initialization();
	// 该轮所有染色体适应度的最大值和index 
	double *best_fitness_index;
	best_fitness_index = max_in_community(chromosome_fitness, chromosome_size); 
	// 初始化平均值 
	average_epoch_best_fitness[0] = sum(chromosome_fitness)/chromosome_size;
	// 初始化每代最佳染色体与适应度对应的适应度 
	all_best_fitness = best_fitness_index[0];
	all_best_epoch = 0;
	for(int i = 0; i < chromosome_length; i++)
		epoch_best[i] = chromosome[(int)best_fitness_index[1]][i];

	//————————————Step2————————————//
	//————————————迭代————————————// 
	for(int i=0; i<epoch_max; i++){
		// 选择交叉变异一套搞定 
		select(chromosome);
		cross(chromosome);
		mutation(chromosome);
	
		//更新一系列数据 
		// 计算操作后染色体对应的适应度
		for(int j=0; j<chromosome_size; j++)
			chromosome_fitness[j] = fitting(chromosome[j]);
		// 计算平均值
		average_epoch_best_fitness[i+1] = sum(chromosome_fitness)/chromosome_size;
		// 储存最优表现 
		best_fitness_index = max_in_community(chromosome_fitness, chromosome_size); 
		if(best_fitness_index[0] > all_best_fitness){
			all_best_fitness = best_fitness_index[0];
			for(int k=0; k<chromosome_length; k++)
				epoch_best[k] = chromosome[(int)best_fitness_index[1]][k];
			all_best_epoch = i+1;
		}
		printf("第%d次迭代:(%lf,%lf)\t%lf\n", i, chromosome[(int)best_fitness_index[1]][0], chromosome[(int)best_fitness_index[1]][1], best_fitness_index[0]);
	}
}



int main(){
	//程序开始和结束时间
	clock_t begin, end;
	begin = clock(); //开始计时 
	srand((unsigned)time(NULL)); // 初始化随机数种子
	// 搜索 
	genetic();
	end = clock();
	printf("计算总耗时:%lf秒\n", ((double)(end-begin))/CLOCKS_PER_SEC);
	printf("遗传算法进化了%d次,最优值为:%lf,最优值在第%d代取得,此代的平均最优值为%lf.\n",epoch_max,all_best_fitness,all_best_epoch,average_epoch_best_fitness[all_best_epoch]);
    printf("取得最优值的地方为(%lf,%lf).\n",epoch_best[0],epoch_best[1]);
	return 0;
}

求解极值 matlab 语言实现

画图

  不是很确定最后的结果对不对,拿matlab试试结果。先浅浅画俩图:

	fsurf(@(x,y)sin(x)/x+sin(y)/y)
	fsurf(@(x,y)(6.452*(x+0.125*y)*(cos(x)-cos(2*y))^2/(0.8+(x-4.2)^2+2*(y-7)^2)^(1/2))+3.226*y)

  例题1

在这里插入图片描述

  例题2:

在这里插入图片描述

GA 求解

  matlab自带 ga 求解工具计算极值,因为这个东西只能算最小值,所以对原函数取负号,得到的最小值再取负就是我们要的最大值:

	[x,fval] = ga(@(x)-(sin(x(1))/x(1)+sin(x(2))/x(2)),2,[],[],[],[],[-10,-10],[10,10], [], gaoptimset('CrossoverFraction', 0.6))
	[x,fval] = ga(@(x)-(6.452*(x(1)+0.125*x(2))*(cos(x(1))-cos(2*x(2)))^2/(0.8+(x(1)-4.2)^2+2*(x(2)-7)^2)^(1/2)+3.226*x(2)),2,[],[],[],[],[0,0],[10,10], [], gaoptimset('CrossoverFraction', 0.6))

  运行结果如下:

在这里插入图片描述
在这里插入图片描述

结论

  经过证明,我们C语言的实现是没什么大问题的。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铖铖的花嫁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值