版权声明:本文为博主原创文章,转载请注明出处。
先解释下什么是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
自己写的这个问题还是比较大,从结果上看要大规模长时间的计算才能计算出部分解,也和算法本身有关,其实也和算法设计中缺乏剪枝操作,没有删去一些显而易见的错误解,导致计算时间过长,同时浪费了部分空间。留待日后改正吧。