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

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、x2string 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_schro_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-33bitset<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++,希望大佬们给点鼓励,原谅我这写的跟屎一样的代码。

转载需标明出处,谢谢

手撕:经典问题的遗传算法代码相关推荐

  1. 手撕python_Pytorch手撕经典网络之LeNet5

    下图为经典网络LeNet5的网络结构. 实现时,主要包括俩个卷积层,俩个pooling层,三个全连接层(严格来说,最后一层的Gaussian connection应有其它的转化方式,这里用全连接). ...

  2. 深度理解递归,手撕经典递归问题(汉诺塔,青蛙跳台阶),保姆级教学。

    目录 序言: 一.函数递归( recursion) 二.递归的两个必要条件 三.递归小问题 (1)接受一个整型值(无符号),按照顺序打印它的每一位 (2)编写函数不允许创建临时变量,求字符串的长度(利 ...

  3. 手撕设计模式之「简单工厂模式」(Java描述)

    前言 利用简单工厂模式可以实现对象创建和业务逻辑处理的分离,但存在工厂类职责过重,增添新产品违背开闭原则等问题.它不属于GoF 23种设计模式之一,但是它可以作为学习工厂方法模式前的一个很好的引导. ...

  4. 手撕设计模式之「工厂方法模式」(Java描述)

    前言 工厂方法模式是对简单工厂模式的改进,它通过对工厂类进行抽象形成一个抽象工厂接口,再让具体的工厂负责对应产品的创建,使得在增加产品的场景中也满足"开闭原则".希望通过本文的学习 ...

  5. 深度学习二 —— 手撕激活函数(阶跃函数、sigmoid、tanh、ReLu、Leaky ReLu)

    文章目录 手撕激活函数 1. 阶跃函数 公式 代码 2. sigmoid 公式 代码 3. 阶跃函数 与 sigmoid函数比较 相同点 不同点 4. tanh 函数 公式 代码 5. sigmoid ...

  6. 手撕代码之七大常用排序算法 | 附完整代码

    点击上方↑↑↑蓝字关注我们~ 「2019 Python开发者日」全日程揭晓,请扫码咨询 ↑↑↑ 0.导语 本节为手撕代码系列之第一弹,主要来手撕排序算法,主要包括以下几大排序算法: 直接插入排序 冒泡 ...

  7. 手撕 CNN 经典网络之 VGGNet(PyTorch实战篇)

    大家好,我是红色石头! 在上一篇文章: 手撕 CNN 经典网络之 VGGNet(理论篇) 详细介绍了 VGGNet 的网络结构,今天我们将使用 PyTorch 来复现VGGNet网络,并用VGGNet ...

  8. 数据与广告系列十一:从性别预测的CASE开始手撕机器学习代码

    作者|黄崇远(题图:ssyer.com,CCO协议)  公号,数据虫巢(ID: blogchong) " 说好的带你们手撕代码. 阅读本文预计需要...我哪知道要多久,反正有点长,看你的理解 ...

  9. 深度学习之手撕深度神经网络DNN代码(基于numpy)

    声明 1)本文仅供学术交流,非商用.所以每一部分具体的参考资料并没有详细对应.如果某部分不小心侵犯了大家的利益,还望海涵,并联系博主删除. 2)博主才疏学浅,文中如有不当之处,请各位指出,共同进步,谢 ...

  10. 和12岁小同志搞创客开发:手撕代码,做一款遥控灯

    机缘巧合在网上认识一位12岁小同志,从零开始系统辅导其创客开发思维和技巧. 项目专栏:https://blog.csdn.net/m0_38106923/category_11097422.html ...

最新文章

  1. 小学生 python教程-Python最佳学习路线图
  2. 实现windows标准的选择文件夹功能
  3. C语言:一个数组中只有两个数字是出现一次
  4. (转载)Android性能优化典范
  5. 添加/移除事件处理程序
  6. 福利来了!国内TOP3的超级云计算,免费领2000核时计算资源!
  7. 【万能小说分析】【python】【词频分析】【词频统计】【jieba】【matplotlib】【wordcloud】【绘图】
  8. 基于canvas的图片压缩函数实现
  9. 【2018.4.21】模拟赛之三-ssl2404 上学【深度优先搜索】
  10. 10一个应用阻止关机贴吧_手机该不该每天关机一次?看完才知道这么多年白用了...
  11. c struct 对齐_C中的struct大小| 填充,结构对齐
  12. 奇怪的比赛--蓝桥杯
  13. python函数的四个特点_Python面向对象三大特征之封
  14. mysql 查看锁表_MySQL的四种事务隔离级别
  15. 矩阵迹的几何意义是什么?
  16. hello guass
  17. 直流屏电源模块GF22007-2高频充电模块R22007
  18. 手机无法连接wifi,提示正在获取ip, ip分配失败
  19. ABBYY FineReader 15 安装教程
  20. 手机语言 Symbian 术语表

热门文章

  1. 1分钟学会系统安装方法,win7 XP win10 win11都变得非常简单
  2. label 详细用法
  3. 增量式编码器和绝对式编码器,ABI信号和UVW信号、编码器PWM信号
  4. solidworks创新作业无限魔方
  5. thymeleaf模板引擎即时生效的问题
  6. 业务与信令-第5章VoLTE原理
  7. html京东 重置代码,拟写京东登录界面(HTML - CSS)
  8. 9.5.4英语词典。设计字典记录小张新学的英文单词和中文翻译,并能根据英文来查找中文翻译,当用户输入1,按提示添加新的单词和中文;用户输入2可查找英文单词的对应中文翻译,输入3,退出程序。
  9. HP打印机无线网共享打印方法(型号P1007)
  10. 在vue项目中插入视频