话不多说,进入主题,作者为了写这三篇已经要饿死了。
更新.
*附上运行截图
*吐槽一下TSP的交叉算法,交叉算法相比区间倒置的变异操作来说效率差太多了,还耗费了好大精力。。
交叉算法
交叉算法其实说起来很简单,就是将两个选择的个体的随机DNA片段交换。
简单吧,但是在具体实现起来还是有一些细节问题。比如新过来的DNA片段可能有重复.
个体1DNA: | 1 | 2 | 3 | 4 | 5 | 6 |
个体2DNA: | 2 | 3 | 5 | 1 | 6 | 4 |
假设两个个体的第3,4位DNA片段要交换(上述加粗部分),则交换后为:
个体1DNA: | 1 | 2 | 5 | 1 | 5 | 6 |
个体2DNA: | 2 | 3 | 3 | 4 | 6 | 4 |
可见,这样的交换出现了两个问题:
1)重复:第一个个体中”5”,”1”与原DNA片段重复,第二个个体中的”3”,”4”与其原DNA片段中的”3”,”4”重复
2)缺失: 第一个个体缺少”3”,”4”,第二个个体缺少”5”,”1
缺失和重复的原因是:把自己的部分基因交出去的同时,得到的交叉基因不一定和损失的基因相同(不计次序),所以交叉后,损失的基因确确实实给了对方个体,而得到的基因却与原有的冲突。
解决办法是:
1)记录下真正损失的基因,即在对方的交叉基因中不重复的基因。上例中即:
第一个个体真正损失: | 5 | 1 |
第二个个体真正损失: | 3 | 4 |
这个例子中还没有变化,如果交叉基因有重复的,则不一样了,如:
个体1DNA: | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
个体2DNA: | 2 | 3 | 5 | 1 | 6 | 4 | 8 | 7 |
此时交叉基因中出现了重复部分: “5”,”6”,”4”,这些基因在交叉中看似损失,其实又补充回来了。所以就不需要记录,此时的损失基因为:
第一个个体真正损失: | 3 |
第二个个体真正损失: | 1 |
2)记录下来真正的损失基因后,我们要找到“这些基因在对方DNA中的映射位置”,如图:
3)记录下来映射位置之后,我们就可以把交叉区间基因直接交换
4)交叉完毕后,根据“真正损失的基因”,“损失基因的映射”,把交叉后的基因在修正,就可以得到正确的交叉基因.
下面给出相关代码:
void GenomeTSP::copulation(Individual &individual1, Individual &individual2){
//获取变异区间
pair<int,int> randomCopuSec=getRandomCopuSec();
//保存第一个个体的交叉区间DNA片段
DNA firstIndiDNASec;
copyDNASeg(individual1._DNA,
randomCopuSec.first,
randomCopuSec.second,
firstIndiDNASec);
//保存第2个个体的交叉区间DNA片段
DNA secIndiDNASec;
copyDNASeg(individual2._DNA,
randomCopuSec.first,
randomCopuSec.second,
secIndiDNASec);
//去除交叉DNA片段的重复元素
DNA uniqueFirstIndiDNASeg=removeDepuInDNA(firstIndiDNASec,secIndiDNASec);
DNA uniqueSecIndiDNASeg=removeDepuInDNA(secIndiDNASec,firstIndiDNASec);
//获取第二个DNA 映射到 第一个个体 的坐标
DNA secDNAMapFirstIndividual=mapDNAToIndiPos(individual1,uniqueSecIndiDNASeg);
//获取第一个DNA 映射到 第二个个体 的坐标
DNA firstDNAMapSecIndividual=mapDNAToIndiPos(individual2,uniqueFirstIndiDNASeg);
//用第二个交叉DNA替换第一个个体的交叉区间DNA
replaceDNASeg(secIndiDNASec,
0,
secIndiDNASec.size()-1,
individual1._DNA,
randomCopuSec.first,
randomCopuSec.second);
//用第一个交叉DNA 替换 第二个个体的交叉区间DNA
replaceDNASeg(firstIndiDNASec,
0,
firstIndiDNASec.size()-1,
individual2._DNA,
randomCopuSec.first,
randomCopuSec.second);
/*消除第一个个体因为交叉产生的DNA片段冲突,
*因为第一个个体获得了第二个个体的部分DNA,所以可能与原来第一个个体的DNA冲突
*所以需要保存新获得的DNA在第一个个体相同元素的对应位置,
*再用第一个个体因为交叉而损失的DNA补充相应的位置
*/
replaceDNASegByVec(uniqueFirstIndiDNASeg,individual1._DNA,secDNAMapFirstIndividual);
replaceDNASegByVec(uniqueSecIndiDNASeg,individual2._DNA,firstDNAMapSecIndividual);
}
void GenomeTSP::copyDNASeg(DNA srcDNAVec, int minPos, int maxPos, DNA &destDNAVec){
for(int i=minPos;i<=maxPos;++i){
destDNAVec.push_back(srcDNAVec.at(i));
}
}
//计算映射地址
vector<int> GenomeTSP::mapDNAToIndiPos(Individual &individual, DNA &DNASegVec){
//返回vector
DNA mapPos;
//遍历DNASegVec,寻找每一个元素在individual。dna中对应的位置
for(size_t i=0;i<DNASegVec.size();++i){
for(size_t j=0;j<individual._DNA.size();++j){
if(DNASegVec.at(i)==individual._DNA.at(j)){//找到相同元素,退出内层循环,保存位置
mapPos.push_back(j);
break;
}
}//iner-loop
}//outer-loop
return mapPos;
}
//生成交叉区间
pair<int,int> GenomeTSP::getRandomCopuSec(){
//随机生成交叉位置,(交叉从0到copuPos之间的DNA片段将会被交换)
int minPos=rand()%MAX_DNA_SIZE;
int maxPos=rand()%MAX_DNA_SIZE;
//保证不重复
while (maxPos<=minPos || (maxPos-minPos)>MAX_COPULATION_SIZE){
maxPos=rand()%MAX_DNA_SIZE;
}
return make_pair(minPos,maxPos);
}
//计算真正损失的DNA片段
DNA GenomeTSP::removeDepuInDNA(DNA firstDNA, DNA secDNA){
DNA result;
for (size_t i=0;i<firstDNA.size();++i){
if( find(secDNA.begin(),secDNA.end(),firstDNA.at(i))==secDNA.end() ){//如果是unique元素
result.push_back(firstDNA.at(i));
}
}
return result;
}
重要代码基本上讲解完毕,剩下的都是一些小细节,就不在讲解。
接下来附上代码。