版權聲明:本文為博主原創文章,轉載請注明出處。
先解釋下什么是8皇后問題:在8×8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。在不考慮翻轉和旋轉等價的情況下,8皇后問題共有96個不同的解。
而n皇后問題就是將8*8的棋盤換為n*n的棋盤,同時擺放n個皇后使之不能相互攻擊。
常用的解法是回溯法,通過不斷遞歸的嘗試來一個一個放置棋子,這種方法其實規避了很多不成立的情況,所以控制了一些解空間的范圍,但是這種方法試圖在一段程序當中將所有解求出來,隨着n的變大,解空間在急速變大,遞歸的巨大空間開銷會讓求解變得很困難,效率會下降很多。
遺傳算法也可以用來解決n皇后問題,但是遺傳算法的本質是根據適應值來選擇和制造更多的靠近目標情況的解,所以不一定能得到所有的解,同時也不能知道對於確定的n皇后問題的解的個數。在這種情況下的遺傳算法有一些暴力破解的因素在其中。
下面談一談幾個關鍵問題的解決(以8皇后問題為例)。
1、編碼問題
我采用的是整數編碼,染色體長度(基因位的個數)等於8,每一位為一個整數(該整數≥0,<8*8),且不能相同,每一個基因位表示的就是一個棋子擺放的位置。
2、適應值的計算問題
適應值的評價標准為發生沖突的個數n的倒數,即沖突越多,適應值越低,不發生沖突時適應值為1(1/1),但是這種評價也存在一定的問題,就是隨着沖突的增多,適應值的減小會變的沒那么明顯(比如說不沖突適應值為1,沖突一個為0.5,沖突2個為0.333,沖突三個為0.25,沖突四個為0.2),所以選擇的力度會相對較弱。可以考慮改為其他的方式進行評價。
3、選擇問題
采用的是線性排名選擇方式,因為上述原因,采用線性排名選擇策略會一定程度上抵消掉適應值計算的問題。
4、突變問題
突變采用的是自適應性變異,即越收斂搜索范圍越小的方法。
下面給出詳細代碼
#include <iostream> #include <algorithm> #include <vector> using namespace std; #define popSize 10000 #define maxGen 200 #define Pc 0.7 #define Pm 0.3 #define Pa 1.1 #define Pb 0.2 int chromSize=0; class indivadual { public: indivadual(){chrom.resize(chromSize);}; vector<int> chrom; double fitness; bool operator ==(const indivadual&i) { int t=0; for (t=0;t<chromSize;t++) { if (find(chrom.begin(),chrom.end(),i.chrom[t])==chrom.end()) { return false; } } return true; } }; class Evalution { private: vector<indivadual> Population; indivadual Best; int BestIndex; indivadual Worst; int WorstIndex; indivadual HistoryBest; double avg; void Initialization(); void SelectPop(); void CrossPop(); void VaryPop(); void Optimizepop(){}; public: Evalution(); void Evalute(); void NextPopulation(); void OutPut(); vector<indivadual> good; int gen; }; Evalution::Evalution() { Initialization(); } void Evalution::Initialization() { int i=0,j=0; Population.resize(popSize); for (i=0;i<popSize;i++) { j=0; while (j<chromSize) { int n=rand(); n=n%(chromSize*chromSize); if (find(Population[i].chrom.begin(),Population[i].chrom.end(),n)==Population[i].chrom.end()||n==0) { Population[i].chrom[j]=n; j++; } } //發的 } Worst=Best=Population[0]; WorstIndex=BestIndex=0; gen=0; avg=0; Best.fitness=0; Worst.fitness=0; } void Evalution::Evalute() { int index=0; for (index=0;index<popSize;index++) { //適應值最大為1 Population[index].fitness=1; //橫坐標 vector<int> x; x.resize(chromSize); //縱坐標 vector<int> y; y.resize(chromSize); int i=0,j=0,q=0,p=0; for (j=0;j<chromSize;j++) { p=Population[index].chrom[j]/chromSize; x[j]=p; q=Population[index].chrom[j]%chromSize; y[j]=q; } for (i=0;i<chromSize;i++) { for (j=i+1;j<chromSize;j++) { if (x[i]==x[j]) { Population[index].fitness++; } } } for (i=0;i<chromSize;i++) { for (j=i+1;j<chromSize;j++) { if (y[i]==y[j]) { Population[index].fitness++; } } } //取交叉數目的倒數為適應值 Population[index].fitness=1/Population[index].fitness; if (Population[index].fitness==1&&find(good.begin(),good.end(),Population[index])==good.end()) { good.push_back(Population[index]); } //更新當代最佳 if (Best.fitness<Population[i].fitness) { Best=Population[index]; BestIndex=index; } //更新當代最差 if (Worst.fitness>Population[index].fitness) { Worst=Population[index]; WorstIndex=index; } } //更新歷史最佳 if (HistoryBest.fitness<Best.fitness) { HistoryBest=Best; } //計算平均值 for (index=0;index<popSize;index++) { avg+=Population[index].fitness; } avg/=popSize; //代數更替 gen++; } void Evalution::NextPopulation() { //選擇 SelectPop(); //交叉 CrossPop(); //變異 VaryPop(); //評價 Evalute(); //優化 Optimizepop(); } //輸出 void Evalution::OutPut() { cout<<"當前代數"<<gen<<" 平均值"<<avg<<" 最好個體適應值"<<Best.fitness<<"最好個體基因"; int i=0; for (i=0;i<chromSize;i++) { cout<<Best.chrom[i]<<" "; } cout<<endl; } //sort函數的輔助函數 bool compare(indivadual a,indivadual b) { if (a.fitness>b.fitness) { return true; } if (a.fitness>b.fitness) { return false; } return false; } //線性排名選擇 void Evalution::SelectPop() { sort(Population.begin(),Population.end(),compare); double p[popSize],selection[popSize]; indivadual newPopulation[popSize]; double FitSum=0; int i=0,j=0,index=0,popindex=0; //計算分配概率 for (i=0;i<popSize;i++) { j=i+1; p[i]=(Pa-Pb/(j+1))/j; } //求分配概率的總和 for(index=0;index<popSize;index++) { FitSum+=p[index]; } //確定輪盤分布 for(index=0;index<popSize;index++) { selection[index]=p[index]/FitSum; } for(index=1;index<popSize;index++) { selection[index]=selection[index]+selection[index-1]; } //用輪盤進行隨機選取,形成新的種群 for(popindex=0;popindex<popSize;popindex++) { double n= (rand()%100)/100.0; index=0; while(n>selection[index]) index++; newPopulation[popindex]=Population[index]; } //將剛產生的群體替換為系統的群體 for(index=0;index<popSize;index++) { Population[index]=newPopulation[index]; } } //雜交算子,離散雜交 void Evalution::CrossPop() { int index=0,position=0,i=0,temp=0,t=0; for(;index<popSize;index++) { indivadual temp; int r=rand()%popSize; temp=Population[index]; Population[index]=Population[r]; Population[r]=temp; } for(index=0;index<popSize;index+=2) { t=rand()%1000/1000.0; if (t<Pc) { position=rand()%chromSize; for (i=position+1;i<chromSize;i++) { temp=Population[index+1].chrom[i]; Population[index+1].chrom[i]=Population[index].chrom[i]; Population[index].chrom[i]=temp; } } } } //變異算子,自適應性變異 void Evalution::VaryPop() { int i=0,j=0,up=chromSize*chromSize-1,down=0; for (i=0;i<popSize;i++) { for (j=0;j<chromSize;j++) { double r=rand()%1000/1000.0; if (r<Pm) { double t=1-Population[i].fitness*0.9999/Best.fitness; //突變區間 int u=(1-pow(r,pow(t,2)))*(up-Population[i].chrom[j]); if (u>up) { u=up; } if (u<down) { u=down; } int l=(1-pow(r,pow(t,2)))*(Population[i].chrom[j]-down); if (l>up) { l=up; } if (l<down) { l=down; } int p=rand()%2; if (p==0) { Population[i].chrom[j]=u; } else Population[i].chrom[j]=l; } } } } int main() { cout<<"n="; int n=0; cin>>n; chromSize=n; Evalution eva; eva.Evalute(); eva.OutPut(); while(eva.gen<maxGen) { eva.NextPopulation(); eva.OutPut(); } int i=0,j=0; cout<<"解共有"<<eva.good.size()<<endl; for (i=0;i<eva.good.size();i++) { for (j=0;j<chromSize;j++) { cout<<eva.good[i].chrom[j]<<" "; } cout<<endl; } }
特別要注意的是變異參數的Pm的值需要調的相對較大,因為需要搜索更大的空間范圍。
種群個數10000,最大代數為100
種群個數10000,最大代數200
種群個數20000,最大代數100
種群個數20000,最大代數200
自己寫的這個問題還是比較大,從結果上看要大規模長時間的計算才能計算出部分解,也和算法本身有關,其實也和算法設計中缺乏剪枝操作,沒有刪去一些顯而易見的錯誤解,導致計算時間過長,同時浪費了部分空間。留待日后改正吧。