遗传算法 TSP问题 C++实现 (三)

话不多说,进入主题,作者为了写这三篇已经要饿死了。


更新.
*附上运行截图
*吐槽一下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中的映射位置”,如图:
遗传算法交叉映射1

3)记录下来映射位置之后,我们就可以把交叉区间基因直接交换

遗传算法交叉映射2
4)交叉完毕后,根据“真正损失的基因”,“损失基因的映射”,把交叉后的基因在修正,就可以得到正确的交叉基因.
遗产算法交叉映射3

下面给出相关代码:


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;
}

重要代码基本上讲解完毕,剩下的都是一些小细节,就不在讲解。

接下来附上代码。

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值