最近写了一个亦可赛艇的遗传算法。什么是遗传算法呢?ios
遗传算法(Genetic Algorithm)是模拟达尔文生物进化论的天然选择和遗传学机理的生物进化过程的计算模型,是一种经过模拟天然进化过程搜索最优解的方法。遗传算法是从表明问题可能潜在的解集的一个种群(population)开始的,而一个种群则由通过基因(gene)编码的必定数目的个体(individual)组成。每一个个体其实是染色体(chromosome)带有特征的实体。染色体做为遗传物质的主要载体,即多个基因的集合,其内部表现(即基因型)是某种基因组合,它决定了个体的形状的外部表现,如黑头发的特征是由染色体中控制这一特征的某种基因组合决定的。所以,在一开始须要实现从表现型到基因型的映射即编码工做。因为仿照基因编码的工做很复杂,咱们每每进行简化,如二进制编码,初代种群产生以后,按照适者生存和优胜劣汰的原理,逐代(generation)演化产生出愈来愈好的近似解,在每一代,根据问题域中个体的适应度(fitness)大小选择(selection)个体,并借助于天然遗传学的遗传算子(genetic operators)进行组合交叉(crossover)和变异(mutation),产生出表明新的解集的种群。这个过程将致使种群像天然进化同样的后生代种群比前代更加适应于环境,末代种群中的最优个体通过解码(decoding),能够做为问题近似最优解。算法
流程图:dom
随机生成一组染色体。函数
根据适应度函数计算个体在繁衍竞争中的适应值。优化
选择出可以将染色体传递给下一代的父母,其余的皆为淘汰者。编码
选择机制应该知足:
1)适应值越高的个体越可能繁殖后代(使用轮盘赌选择)。
2)适应值最高的个体可以绝对保留(精英策略,避免退化)。spa
即产生下一代。使用单点交叉法,以某个点为中心,交叉交换染色体,以产生新个体。firefox
对于每一个新产生的个体,根据变异几率进行变异,即染色体的某一位发生随机变化。code
那个人遗传算法是干什么的呢?用一百个半透明彩色三角形,叠加出一张目标图片。orm
#include<iostream> #include<cstring> #include<cstdlib> #include<vector> #include<cstdio> #include<ctime> #include<cmath> using namespace std; const unsigned randmx=0xffffffff; unsigned x,y,z,w; unsigned random(){//随机数生成器 unsigned t=x^(x<<11); x=y; y=z; z=w; return w=w^(w>>19)^t^(t>>8); } struct color{//颜色 unsigned char b,g,r;//RGB,因为BMP文件格式是BGR,所以以此顺序 color(unsigned char x=255,unsigned char y=255,unsigned char z=255){r=x;g=y;b=z;} }; struct trngl{//三角形 struct vct{//二维向量 int x,y; vct(int xx=0,int yy=0){x=xx;y=yy;} vct operator-(const vct &b){return vct(x-b.x,y-b.y);}//减法 int operator*(const vct &b){return x*b.y-y*b.x;}//只考虑长度的叉乘 void rand(int h,int w){x=random()%h+1;y=random()%w+1;}//随机 }; vct a,b,c;//三个顶点 color co;//颜色 trngl(int ax,int ay,int bx,int by,int cx,int cy,color cl):a(vct(ax,ay)),b(vct(bx,by)),c(vct(cx,cy)),co(cl){} trngl(vct aa=vct(),vct bb=vct(),vct cc=vct(),color cl=color()):a(aa),b(bb),c(cc),co(cl){} int area(){return abs((b-a)*(c-b));}//面积 bool vctin(vct p){return trngl(p,a,b).area()+trngl(p,a,c).area()+trngl(p,b,c).area()==area();}//判断点是否在三角形内,面积法 bool vctin(int px,int py){return vctin(vct(px,py));} void rand(int h,int w){//随机形状 a.rand(h,w); b.rand(h,w); while(a.x==b.x&&a.y==b.y)b.rand(h,w); c.rand(h,w); while((a.x==c.x&&a.y==c.y)||(b.x==c.x&&b.y==c.y))c.rand(h,w); } void cord(){co=color(random()%256,random()%256,random()%256);}//随机颜色 }; struct genetic{ static const int h=80,w=80,trn=100,maxp=100;//高,宽,三角形数,最大种群大小 static const double chgp=0.003,cgcp=0.005;//形状变异几率,颜色变异几率 template<int H,int W,int T> struct pic{//一个图像 trngl trns[T+1];//三角形 color co[H+1][W+1];//每一个像素的颜色 int con[H+1][W+1],bes;//每一个像素上的三角形数,适应值 pic(){bes=0;} void init(){//从三角形初始化图形 for(int i=1;i<=H;i++){ for(int j=1;j<=W;j++){//全部三角形颜色取平均 int r=0,g=0,b=0; con[i][j]=0; co[i][j]=color(); for(int t=1;t<=T;t++){//累计 if(trns[t].vctin(i,j)){ r+=trns[t].co.r; g+=trns[t].co.g; b+=trns[t].co.b; con[i][j]++; } } if(con[i][j])co[i][j]=color(b/con[i][j],g/con[i][j],r/con[i][j]); } } } //与目标比较颜色差别得出适应值 void comp(pic &b){for(int i=1;i<=H;i++)for(int j=1;j<=W;j++)bes+=abs(co[i][j].r-b.co[i][j].r)+abs(co[i][j].g-b.co[i][j].g)+abs(co[i][j].b-b.co[i][j].b);} void read(FILE *fin){//从文件读BMP文件 short tmps[27]; unsigned char tmpc; fread(tmps,sizeof(short),27,fin);//没什么卵用的文件文件头 for(int i=H;i>=1;i--){//BMP是从下至上存储 fread(co[i]+1,sizeof(color),W,fin);//读像素点 if(W*3%4)for(int j=0;(W*3+j)%4;j++)fread(&tmpc,sizeof(char),1,fin);//对齐为整数个字(四字节) } } void turnbmp(int g){//输出为BMP char f[21]; sprintf(f,"%d.bmp",g); FILE *fout=fopen(f,"wb"); short BM=0x4d42,pl=1,cl=24; int wt=W*3/4*4,siz=H*(wt+(W*3%4?4:0))+54,ep=0,to=0x36,si=0x28,h=H,w=W; unsigned char epc=0; fwrite(&BM,sizeof(short),1,fout);//BM,文件类型标识 fwrite(&siz,sizeof(int),1,fout);//文件大小,文件头54字节+对齐后的主数据区大小 fwrite(&ep,sizeof(int),1,fout);//空 fwrite(&to,sizeof(int),1,fout);//偏移量,没有调色板的状况下为54 fwrite(&si,sizeof(int),1,fout);//文件信息大小,为40 fwrite(&w,sizeof(int),1,fout);//宽 fwrite(&h,sizeof(int),1,fout);//高 fwrite(&pl,sizeof(short),1,fout);//平面数,显然为1 fwrite(&cl,sizeof(short),1,fout);//颜色类型,这里为24位 fwrite(&ep,sizeof(int),1,fout);//表示图片的压缩属性,bmp图片是不压缩的,为0 fwrite(&ep,sizeof(int),1,fout);//表示bmp图片数据区的大小,当上一个数值为0时,这里的值能够省略不填,为0 fwrite(&ep,sizeof(int),1,fout);//表示图片X轴每米多少像素,可省略,为0 fwrite(&ep,sizeof(int),1,fout);//表示图片Y轴每米多少像素,可省略,为0 fwrite(&ep,sizeof(int),1,fout);//索引图使用了多少个索引,这里为0 fwrite(&ep,sizeof(int),1,fout);//表示有多少个重要的颜色,0表示全部的颜色都很重要 for(int i=H;i>=1;i--){//从下至上存储 fwrite(co[i]+1,sizeof(color),W,fout); if(W*3%4)for(int j=0;(W*3+j)%4;j++)fwrite(&epc,sizeof(char),1,fout);//对齐 } fclose(fout); } void rand(){//随机 for(int i=1;i<=T;i++){ trns[i].rand(h,w); trns[i].cord(); } init(); } pic cross(pic &b){//交叉,单点交叉法 int c=random()%(T-1)+1;//随机交叉点 pic<H,W,T>ans; for(int i=1;i<=T;i++)ans.trns[i]=(i<=c?trns[i]:b.trns[i]);//从一个点交换 ans.init(); return ans; } void mutate(){//变异 for(int i=1;i<=T;i++){ if(double(random())/randmx<=cgcp)trns[i].cord();//颜色变异 else if(double(random())/randmx<=chgp)trns[i].rand(h,w);//形状变异 } init(); } }; pic<h,w,trn>best;//目标 vector< pic<h,w,trn> >popu,inv;//种群,父母 bool kil[maxp];//淘汰的个体 int bes[maxp];//适应值前缀和 genetic(){ FILE *be=fopen("best.bmp","rb");//读入目标 best.read(be); fclose(be); for(int i=0;i<maxp;i++){//随机种群 kil[i]=false; pic<h,w,trn>ne; ne.rand(); ne.comp(best); popu.push_back(ne); } } void start(){//开始 int age=0,sq; while(true){//下一代 age++; int maxi=-1,minn=0x7fffffff,maxn=0; for(int i=0;i<maxp;i++){//计算适应值前缀和以划分区间 if(popu[i].bes<minn){//维护最小值 minn=popu[i].bes; maxi=i; } if(popu[i].bes>maxn)maxn=popu[i].bes;//维护最大值 bes[i]=(!i?0:bes[i-1])+popu[i].bes; } sq=sqrt(age); if(sq*sq==age){//是否为彻底平方数 printf("sqrt:%d\tAGE:%d\tBEST:%d\tDIFFERENCE:%d\n",sq,age,minn,maxn-minn);//平方根,代数,最好个体的适应值,适应值最大差距 popu[maxi].turnbmp(age);//输出 } int kld=0; while(kld<maxp/2){//杀一半 int r=random()%(bes[maxp-1]+1);//选择位置 for(int j=0;j<maxp;j++){ if(bes[j]>=r){//是否选择了这个位置 if(j==maxi)break;//精英策略,不杀最好的 if(!kil[j]){//没杀过就杀了 kil[j]=true; kld++; } break; } } } inv.clear(); for(int i=0;i<maxp;i++){//选择 if(kil[i])kil[i]=false; else inv.push_back(popu[i]); } popu=inv; for(int i=1;i<(signed)inv.size();i++){//交叉,变异 pic<h,w,trn>c=popu[i-1].cross(popu[i]); c.mutate(); c.comp(best); popu.push_back(c); } if(!(maxp%2)){//零头 pic<h,w,trn>c=popu[inv.size()-1].cross(popu[0]); c.mutate(); c.comp(best); popu.push_back(c); } } } }; int main(){ srand(time(0)); x=rand()*rand()*4U+rand();//初始化随机数生成器 y=rand()*rand()*4U+rand(); z=rand()*rand()*4U+rand(); w=rand()*rand()*4U+rand(); genetic g; g.start();//开始 }
目标必须为24位非索引图,可用画图更改格式
此代码效率极其低下,请务必使用-Ofast编译优化以使其速度暴涨。
使用方法详见代码。
图像的进步速度基本和平方成正比,所以全家福中图片都是彻底平方数代数的最优解。
目标:
全家福:
第1~133225代
动图:
目标:
全家福:
第1~112225代
动图: