最近寫了一個亦可賽艇的遺傳算法。什麼是遺傳算法呢?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代
動圖: