遗传算法解TSP(2)-遗传算子

引言

上一章讲解了遗传算法的基本思想与工作流程,构建了物种类Species及评价物种优劣的适应度函数。本章叙述如何利用求得的物种适应度去进行优胜劣汰的“选择算子”、物种间繁衍配对的“交叉算子”以及单个物种基因突变的“变异算子”。

选择算子

1.选择概率

物种si(i=1,2,…,n)的适应度f(si)已然得到,接下来就要利用f(si),求得它在整个种群中被选择(即遗传到下一代)的概率。这个概率表示为:



产生了概率以后,我们便需要进行选择。

2.轮盘赌选择法

采用“轮盘赌选择法”,形象点说,就像我们经常见到的抽奖转转盘一样:



即每次筛选就相当于转动轮盘,概率大的面积就越大,自然就更容易被选上。那么“转”多少次呢?这里就涉及种群容量的约定了,我们如果选得过小,那么物种的多样性不够,很难有机会产生更优秀的物种(就像如果地球上其他生物都灭绝了,只剩人类,那么物种改变的机会,即路线更短的概率相对就越小了),而如果过大,那么算法的效率会降低,随机性更大,最后很不容易收敛。而根据一些文章和经验,每一轮我们就维持上一轮的种群容量大小即可,保持种群总量不变,即由初始种群的大小决定。那初始种群大小又选多大呢?这个参数就需要根据具体问题的规模来进行调整了,如果城市数很少,可以适当小一些,如果很多,就调大点。

3.选择过程

下面举一个实际例子来说明具体怎么选择。假设我们初始化了这样一个种群,也计算了他们的适应度、选择概率,得到如下的表:



从上表很容易看出:物种2因为路线较短所以适应度高,进而经过4次轮赌直接被选中了2次,而较次的物种1被选中了1次,物种3虽然适应度比物种4高,但由于算法的随机性并没有被选上,而是物种4“侥幸”被选上了。所以新的种群应该是这样的:一个物种1、两个物种2和一个物种4,即种群基因为:125367498、325974681、325974681和974613528。

4.精英保留策略

这里我们不难发现一个问题:倘若物种2的选中概率没有51.54%那么高,而是稍低一些(保证仍是种群中适应度最高的物种),那么这时它参与轮赌被选中的次数就难说有2次了,而很可能多多地让那些资质现在并非很好的物种1、3、4遗传到了下一代。但物种2仍是适应度最高的精英物种啊!让它怀才不遇,最后落得个沧海遗珠之憾,的确有失公平。
所以这时,我们加入一个“精英保留策略”,即并非所有物种均参与赌轮,而是在轮赌之前,先选出适应度最高的那个物种,复制若干个后提前进入下一代,接着再让剩余的物种参与赌轮进入下一代,最终两部分合成一个新种群。这样避免了因为概率原因,使得优秀物种沧海遗珠的情况发生,但这样做也容易陷入局部最优,所以多少个精英这个参数就需要不断地调整了,据一些研究经验来看,一般复制1/4效果是比较好的。
下面是整个选择算子的实现代码:

//选择优秀物种(轮盘赌)
void select(List<SpeciesNode> speciesList)
{            
    //计算选择概率
    calRate(speciesList);
    
    //找出最大适应度物种
    float talentFitness=Float.MAX_VALUE;
    SpeciesNode talentSpecies=null;
    for(SpeciesNode species : speciesList)
    {
        if(species.fitness < talentFitness)
        {
            talentFitness = species.fitness;
            talentSpecies = species;
        }
    }
    //将最大适应度物种复制talentNum个
    List<SpeciesNode> newSpeciesList=new ArrayList<SpeciesNode>();
    int talentNum = (int)(speciesList.size() * tp); //tp:复制比重
    for(int i=1;i<=talentNum;i++)
    {
        //复制物种至新表
        SpeciesNode newSpecies=talentSpecies.clone();
        newSpeciesList.add(newSpecies);
    }
    //轮盘赌speciesList.speciesNum-talentNum次
    int roundNum=speciesList.size()-talentNum;
    for(int i=1;i<=roundNum;i++)
    {
        //产生0-1的概率
        float rate=(float)Math.random();
        for(SpeciesNode species : speciesList)
        {
            if(species == talentSpecies || rate > species.rate) //精英物种或未选中,跳过
                rate=rate-species.rate;
            else //该物种在本次轮赌中选中
            {
                SpeciesNode newSpecies=species.clone();
                newSpeciesList.add(newSpecies);        
                break;
            }
        }        
    }
    speciesList = newSpeciesList;
}

交叉算子

交叉是对种群内两物种的基因序列进行裁剪组合的操作,一般以一定概率执行,而不是每次都执行。物种的配对选取最好随机,而不要1和2配对,3和4配对(因为在使用精英保留策略时很可能是连续追加进种群的,这样相当于近亲繁殖,很难擦出火花即产生路线长度比双亲都短的后代基因)。那么双亲的基因序列之间具体怎么交叉呢?
由于物种基因的编码形式是以“城市编号”为元素的,在实现交叉操作时首先任选一个位置作为起点,交换两个物种的后半段基因。但需注意的是,交叉后的后半段基因可能与物种的前半段基因重复,故而还需进行基因冲突处理,即把物种1所有重复的基因与物种2所有重复的基因对应交换。交叉操作具体如下图所示:



具体实现代码如下:

//交叉操作
void crossover(List<SpeciesNode> speciesList)
{
    for(int n=0;n<speciesList.size();n+=2) //两两配对
    {
        if(n+1 >= speciesList.size()) //已无可配对的母物种(种群容量为奇数)
            break; //结束
        
        SpeciesNode fatherSpecies = speciesList.get(n); //父物种
        SpeciesNode motherSpecies = speciesList.get(n+1); //母物种
        
        //交叉概率 pcl < rate < pch
        float rate=(float)Math.random();
        if(rate > Constant.pcl && rate < Constant.pch) 
        {            
            int crossPosition=rand.nextInt(Constant.CITY_NUM); //随机生成交叉点
            List<Integer> fatherDuplicateGenesList = new ArrayList<Integer>(); // 存储父物种前半段所有重复基因的位置
            List<Integer> motherDuplicateGenesList = new ArrayList<Integer>(); // 存储母物种前半段所有重复基因的位置
            
            //后半段基因挨个位置进行互换
            for(int i=crossPosition;i<Constant.CITY_NUM;i++)
            {
                //基因互换
                int gene;
                gene=fatherSpecies.genes[i];
                fatherSpecies.genes[i]=motherSpecies.genes[i];
                motherSpecies.genes[i]=gene;
                
                //检测fatherSpecies.genes[i]是否与父物种前半段某位置基因重复,若是则记录
                for(int j=0;j<crossPosition;j++)
                    if(fatherSpecies.genes[i] == fatherSpecies.genes[j])
                        fatherDuplicateGenesList.add(j);
                
                //母物种同理
                for(int j=0;j<crossPosition;j++)
                    if(motherSpecies.genes[i] == motherSpecies.genes[j])
                        motherDuplicateGenesList.add(j);
            }
            
            //去重
            for(int k=0;k<fatherDuplicateGenesList.size();k++)
            {
                //父、母物种前半段重复的基因对应交换
                int fatherGenePosition = fatherDuplicateGenesList.get(k);
                int motherGenePosition = motherDuplicateGenesList.get(k);
                
                int gene;
                gene=fatherSpecies.genes[fatherGenePosition];
                fatherSpecies.genes[fatherGenePosition]=motherSpecies.genes[motherGenePosition];
                motherSpecies.genes[i]=gene;
            }
        }
    }
}

变异算子

“变异”是跳出局部最优解的一个重要法宝,是对基因序列进行若干变换的一种操作,一般按非常小的概率执行。本文采用的是一种学界普遍认为效果较好的一种变异方式,即随机选取基因序列的两个位置k和m,逆转其k~m间的城市编号,即若现有物种:



随机产生1和n之间的两相异整数k和m,若k<m,执行逆转变换,得到新的基因序列:



下面是代码:
//变异操作
void mutate(List<SpeciesNode> speciesList)
{    
    //每一物种均有变异的机会,以概率pm进行
    for(SpeciesNode species : speciesList)
    {        
        float rate=(float)Math.random();
        if(rate < Constant.pm)
        {
            //寻找逆转的左右端点
            Random rand=new Random();
            int left=rand.nextInt(Constant.CITY_NUM);
            int right=rand.nextInt(Constant.CITY_NUM);
            if(left > right)
            {
                int tmp;
                tmp=left;
                left=right;
                right=tmp;
            }
            
            //逆转left-right下标元素
            while(left < right)
            {
                int tmp;
                tmp=species.genes[left];
                species.genes[left]=species.genes[right];
                species.genes[right]=tmp;
                left++;
                right--;
            }
        }
    }
}

结语

啊~累死了,遗传算法求解TSP的基本思想差不多就是这些啦。下一章将给出GeneticAlgorithm类的一个总控调用流程、遗传算法的一些常量参数定义及算法的实际运行效果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容