手撕:经典问题的遗传算法代码

手撕:经典问题的遗传算法代码

1、遗传算法简介

1.1、基因的表达

1.2、新种群的诞生:交叉

1.3、天选的个体:变异

1.4、自然的选择:轮盘赌

2、算法框架搭建

3、完善每一步的算法实现

4、整体的调试

遗传算法简介

1.1 基因的表达

1、如何去编码

二级标题应用遗传算法的首要问题,就是进行染色体的编码,常用的方式,也是最基本的方式就是采用二进制进行编码,所以,我们首先需要根据实数表示的问题的解,如以下问题的求解:在这里插入图片描述
将x1,x2进行二进制编码:在这里插入图片描述

2、二进制编码的实现

首先是长度,即位数的确定,这个首先取决于我们对于问题解所要求的精度,eg:4位,那么解所在的定义域计算后的实数,再用二进制表达出来,至少需要的位数,便是其长度,
eg:x1的定义域为:-3~12.1,故有(12.1-(-3))*10000=151000
217<151000< 218-1,故为18位,同理,x2为15位,因此该问题解表达为二进制串后是一条33位的染色体。

3、解码

同理,解码时只需将33位的染色体前18位摘出来,转换位十进制数,便是x1,后15位则为x2。

1.2 新种群的诞生:交叉

在一个合适的交叉率的情况下,随机的从种群中选择选出染色体进行交叉,交叉点也是随机的,例如,种群一共十条染色体,那么随机如果选出4条,那么就有两次交叉,但是每一次的交叉点,是随机的,在33位的染色体上,随机的选择某一点作为交叉的点,互换染色体的片段,则完成一个新生,加入种群,当所有被选中的染色体都交叉完成后,就诞生出新的种群。
eg:
设交叉率P=0.7,即平均有70%的染色体会参与产生后代,同样,我们假设染色体还是10条,因此,需要产生十个随机数,赋予每条染色体,若是低于0.7的染色体,将会被选作交配对象,然后对每一次的交叉,随机产生一个1~32的数,例如13,作为交叉点,交换染色体片段,得到子代,和父代一起去接受下一步的天选:变异。

1.3 天选的个体:变异

作为模拟人类遗传,以及自然界的规律的遗传算法,变异必不可少,但是发生概率比较小,但是变异一般又是某个基因发生变异,因此,对于种群中的所有基因而言,它们都是带选择的对象,这里主要是我们把基因二进制化,因此带选择的对象就成了某一位二进制数了,即变异就是0翻转为1,或者1翻转为0。
eg:
假设基因变异的概率为0.01(一般都是小于此数),即种群平均有1%的基因发生变异,或者说每个基因有1%的可能变异,假设此时种群有十条染色体,一共330个基因,那么平均每次有3.3个基因发生变异,对每一个基因都产生一个随机数,若小于0.01,则该基因变异(翻转),产生新的染色体,加入种群,至于适不适合生存,咱看接下来来自大自然的选择:轮盘赌。

1.4、自然的选择:轮盘赌

大自然,在问题中既是我们要求的最优解,自然往往留下的总是优秀的那一批人,那么对于问题的求解,我们总是留下最好的一些解,再从中挑选最符合我们要求的那一个解,因此,检验这个染色体适不适合留下,依据就在于这个染色体对于问题的回答,是不是更好,比如我们的问题是求最大值,那么,染色体对于问题的回答自然是取得最大值的最应该留下,但是,自然选择充满了偶然性,有可能最优秀的,意外而亡,故只能说,越优秀,生存下去的可能就越大,故,采用一种轮盘赌的方式,来模拟大自然的选择,eg:
对每一个染色体都计算它对于问题的回答,即计算适值,然后计算每条染色体在转盘上所占的面积大小(概率大小),然后,每一条染色体都占有一定的面积,越优秀,面积越大,指针选到的几率也就越大,接下来选出10条染色体,作为新种群,淘汰掉未被选中的,故产生10个随机数,去在轮盘上转10次,当然,若是已经选中,则继续加转,直到选出10条不一样的染色体为止。至此,完成一次迭代。

2、算法框架搭建

框架:即我们在实现算法的过程中,都应该需要哪些步骤,哪些步骤适合封装为一个函数去操作,因此,我习惯首先写空函数,相当于写伪代码。
如下:
在这里插入图片描述

3、完善每一步的算法实现

首先是染色体,我设计为一个类,因为类有数据的保护性以及成员函数可对数据操作的方便的特点。

//染色体
class Chromosome
{
private:
	double x1,x2; //染色体片段对应的十进制值x1、x2
	string s1, s2, chro_s;//片段s1,s2,染色体chro_s
public:
	Chromosome(double n1, double n2):x1(n1),x2(n2){}
	void Encode();				//编码函数
	float Eval();				//计算适值
	string get_chro_s() { return chro_s; }
	void input_chro_s(string s) { chro_s = s; }
	void Decode();				//染色体解码
};

然后,是编码,解码的工作

//染色体编码函数
void Chromosome::Encode()
{
	//对X1片段染色体编码
	bitset<18> fragment1((x1 + 3) *(pow(2, 18)-1)/15.1);
	s1 = fragment1.to_string();	
	//cout << s1 << endl;

	//对X2片段染色体编码
	bitset<15> fragment2((x2-4.1)*(pow(2, 15)-1)/1.7);
	s2 = fragment2.to_string();
	//cout << s2 << endl;

	//片段拼接得到染色体字符串:chro_s
	chro_s = s1 + s2;
	cout << chro_s << endl;
	//cout <<"以上运行Encode函数"<< endl;
}
//染色体解码函数
void Chromosome::Decode()
{
	//片段一解码
	//s.substr(pos, n) //截取s中从pos开始的n个字符的子串,并返回
	bitset<18> t1(chro_s.substr(0, 18));
	x1 = (t1.to_ulong()*15.1 / (pow(2, 18)-1) - 3);
	//片段二解码
	bitset<15> t2(chro_s.substr(18, 15));
	x2 = (t2.to_ulong()*1.7 / (pow(2, 15)-1) + 4.1);
	//cout << "x1=" << x1 << " x2=" << x2 << endl;
}

然后是计算适值:

//适值计算函数
float Chromosome::Eval()
{
	Decode();
	return(float)(21.5 + x1 * sin(4 * 3.14*x1) + x2 * sin(20 * 3.14*x2));
}

之后是交叉函数的编写

//交叉函数
void Crossover(vector<Chromosome> &c)
{
	//1、选择交叉的染色体:产生随机数
	vector<Chromosome> cross_chro;
	for (int i = 0; i != Population_m; i++)
	{
		float p=(float)(rand() % (1001)) / 1000;
		//cout << "p=" << p << endl;
		if (p < Crossover_rate)//低于交叉率的染色体交叉
		{
			//记录染色体
			cross_chro.push_back(c[i]);
			//cout << "第" << i << "号染色体选作交叉" << endl;
			//cout << "#" << i << " " << c[i].get_chro_s() << endl;
		}
	}
	//2、子代存储
	int cross_num = 0;
	for (int j = 0; j < (cross_chro.size()/2); j++)
	{
		//cout << "第" << j+1 << "次交叉" << endl;
		int Crossover_point = rand() % (Chro_len + 1);//产生一个随机的交叉点
		//cout << "Crossover_point=" << Crossover_point << endl;
		string temp;
		temp = cross_chro[cross_num].get_chro_s();//做一个备份

		cross_chro[cross_num].input_chro_s(cross_chro[cross_num].get_chro_s().replace(Crossover_point, Chro_len - Crossover_point, cross_chro[cross_num + 1].get_chro_s().substr(Crossover_point, Chro_len - Crossover_point)));
		cross_chro[cross_num + 1].input_chro_s(cross_chro[cross_num + 1].get_chro_s().replace(Crossover_point, Chro_len - Crossover_point, temp.substr(Crossover_point, Chro_len - Crossover_point)));
		//cout << cross_chro[cross_num].get_chro_s() << endl;
		//cout << cross_chro[cross_num + 1].get_chro_s() << endl;
		//cout << "***************************" << endl;
		cross_num += 2;
	}
	//3、将子代染色体融入种群
	for (int i = 0; i != (int)cross_chro.size() / 2 * 2; i++)
	{
		c.push_back(cross_chro[i]);
	}
	cross_chro.clear();
}

然后是变异函数

//变异函数
void Mutation(vector<Chromosome> &c)
{
	vector<float> Gene_m_r;
	//给每个基因产生随机数
	for (int i = 0; i != Chro_len * c.size(); i++)
	{
		Gene_m_r.push_back((float)(rand() % (1001)) / 1000);
	}
	//遍历基因,检测变异基因
	for (int i = 0; i != (Chro_len * c.size()); i++)
	{
		if (Gene_m_r[i] < Mutation_rate)
			//计算基因位置
		{
			int Chromosome_num = i / Chro_len;		//求染色体号数
			int Gene_num = i - Chromosome_num * Chro_len;//求基因位置0-33
			 
			bitset<33> Comp(c[Chromosome_num].get_chro_s());//字符串转二进制串

			Comp = Comp.flip(Gene_num);	//基因变异
			c[Chromosome_num].input_chro_s(Comp.to_string());//获得变异后的基因
			//cout << "变异染色体号数:" << Chromosome_num << "  变异基因位置:" << Gene_num
			//	<< "  变异后的染色体:" << c[Chromosome_num].get_chro_s() << endl;
		}
	}
	Gene_m_r.clear();
}

最后是轮盘赌

//轮盘赌函数
void Roulette(vector<Chromosome> &c)
{
	float Eval_sum = 0;
	//计算适值总和
	for (int i = 0; i != c.size(); i++)
	{
		Eval_sum += c[i].Eval();
	}
	vector<float> Pk;//累计概率的容器,需要清理内存
	float pk = 0;
	//计算累计概率
	for (int i = 0; i != c.size(); i++)
	{
		pk+= c[i].Eval() / Eval_sum;
		Pk.push_back(pk);
	}
	vector<float> chro_r;//需要清理内存
	//产生新种群,染色体随机数
	for (int i = 0; i != Population_m; i++)//新种群为10条染色体,故有十次轮盘指针值
	{
		chro_r.push_back((float)(rand() % (1001)) / 1000);
	}
	int num = 0,flag = 1;//染色体下标,轮盘有效标志位
	int record[Population_m] = { 11,12,13,14,15,16,17,18 };//新种群下标值
	vector<Chromosome> temp = c;//复制一份数据,进行种群更新,更新完需要清理内存
	int k = Population_m;//初始轮盘次数=种群数量
	for (int i = 0; i != k; i++)
	{
		for (int j = 0; j != Population_m; j++)
		{
			if (chro_r[i] <= Pk[j])//轮盘值小于累计概率区间,则输出该区间对应的染色体
			{
				for (int m = 0; m != Population_m; m++)
				{
					if (j == record[m])
					{
						flag = 0;
					}
				}
				if (flag==0)//如果轮盘值选中的染色体已经被选中的,重新再选一次
				{
					chro_r.push_back((float)(rand() % (1001)) / 1000);//重新产生一个随机数
					k++;
					flag = 1;
					break;
				}
				else//轮盘选中染色体
				{
					record[num] = j;
					num++; 
					//**************************需要删除最后的几个元素*************************
					c[num].input_chro_s(temp[j].get_chro_s());//应该需要删除多余的染色体
					break;
				}
			}
		}
	}
	for (int i = c.size(); i > Population_m; i--)
	{
		c.pop_back();
	}
	//清理内存
	Pk.clear();
	chro_r.clear();
	temp.clear();
}

最后还需要增加一个计算当代最优值的函数

//输出当代最优值
float opt_value(vector<Chromosome> &c)
{
	vector<float> value;
	for (int i = 0; i != Population_m; i++)
	{
		value.push_back(c[i].Eval());
	}
	sort(value.begin(), value.end());
	return value[Population_m-1];
	value.clear();
}

4、整体的调试

自己写一个测试函数,把代码跑起来就行了

void test()
{
	int n = N;
	Population_init();//种群初始化完成,准备迭代
	static float max=0;
	static int m=0;
	//开始迭代
	while(n--)
	{
		Crossover(v_chro);//交叉
		Mutation(v_chro);//变异
		Roulette(v_chro);//轮盘
		//输出轮盘选择后的染色体
		for (int i = 0; i != v_chro.size(); i++)
		{
			cout << "#" << i << " " << v_chro[i].get_chro_s() << endl;
		}
		//计算最优值
		float opt_Eval = opt_value(v_chro);
		cout << "第" << N-n << "代最优解为:" << opt_Eval << endl;
		if (opt_Eval > max)
		{
			max = opt_Eval;
			m = n;
		}
		cout << "*******************完成第" << N - n << "次迭代*******************" << endl;
		cout << endl;
	}
	cout << "第" << N - m << "代为最优解:" << max << endl;
}

然后输出后是这个样子:
在这里插入图片描述
来个小视频感受一下:

手撕遗传算法,详见博客

小弟刚学C++,希望大佬们给点鼓励,原谅我这写的跟屎一样的代码。

转载需标明出处,谢谢

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

梦在北灬

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

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

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

打赏作者

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

抵扣说明:

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

余额充值