此为《算法的乐趣》读书笔记,我用javascript(ES6)重新实现算法。
遗传算法
“物竞天择,适者生存”,遗传算法就是借鉴生物体自然选择和自然遗传机制的随机搜索算法。算法的关键点有:基因的选择与编码、适应度评估函数与三个遗传算子(选择、交叉和变异)的设计。
0-1背包问题
有一个背包,最多承重为C=150的物品,现在有7个物品,编号为1~7,重量分别是w=[35,30,60,50,40,10,25],价值分别是p=[10,40,30,50,35,40,30],现在从这7个物品中选择一个或多个装入背包,要求在物品总重量不超过C的前提下,所装入的物品总价值最高。
代码及思路
该算法采用属性序列方式对基因编码,遗传算子则使用了比例选择模式、多点交叉和均匀变异三种方式,麻雀虽小,五脏具全。
变量定义
var C = 150 //背包最大承重var WEIGHT = [35,30,60,50,40,10,25] //物品重量var POWER = [10,40,30,50,35,40,30] //物品价值var LEN = 7 //基因长度var maxPower = 0 //保存最大值方案var maxGene = []var maxi = 0; //最大值最初出现的进化代数const POPMAX = 32, //种群数量 P_XOVER = 0.8, //遗传概率 P_MUTATION = 0.15, //变异概率 MAXGENERATIONS = 20 //总的进化代数var pop = [] //种群所有对象
基因编码
基因由7件物品状态组成,1表示装入,0表示不装入;每个个体除了基因外,还有适应度、选择概率和积累选择概率。类定义如下:
class Gene{ constructor(gene){ this.gene = gene; //基因,数组 this.fitness = 0; this.rf = 0; this.cf = 0; }}
种群初始化
每个个体选择随机的基因,使用0,1随机数直接填充gene数组。因为这个具体问题规模较小,在选择时我丢弃了适应度较高的方案,以此来更好的测试算法的效果。
function initGenes(){ let count = 0, maxFit = 100; //随机生成的基因适应度的最大值 while(count < POPMAX){ let tmp = [],pall = 0; for(let j = 0; j
适应度函数
计算种群中所有对象的适应度及总和,并对超出C的基因进行“惩罚”。
function envaluateFitness(max){ //max参数只是用来记录进化代数 let totalFitness = 0; for(let i=0; iC){ //基因不符合要求,适应降到1,让其自然淘汰 pop[i].fitness = 1; }else{ if(pop[i].fitness > maxPower){ //保存阶段最优值 maxPower = pop[i].fitness; maxGene = __.cloneDeep(pop[i].gene); //使用lodash库 maxi = max; } } totalFitness += pop[i].fitness } return totalFitness;}
选择算子函数
采用简单的轮盘赌方式进行选择,首先计算种群中所有个体的选择概率和累积概率,然后利用随机数进行“轮盘赌”,挑出幸运者作为新种群。这里有个坑,lodash的cloneDeep直接克隆pop会有问题,出现怪异问题,难道是我的对象层次太深,求解!
function selectBetter(totalFitness){ let lastCf = 0; let newPop = [] for(let i = 0; i= pop[j].cf && p < pop[j+1].cf){ newPop[i] = pop[j+1]; break; } } } } pop = [] //种群替换,坑在这,直接 pop=__.cloneDeep(newPop)不对,高手给解释下,谁研究过lodash的源码? for(let i=0; i< newPop.length; i++){ pop.push(__.cloneDeep(newPop[i])) }}
交叉算子函数
交叉算子采用多点交叉策略,对两个随机选中的个体基因进行交换,基因交换的位置和个数都是随机的,使得新个体的基因更具有随机性。
function crossover(){ let first = -1; for(let i=0; i
变异算子函数
变异算子采用均匀变异的策略,选中个体基因变异的个数与位置都是随机选择的。
function mutation(){ for(let i=0; i
算法主流程
主流程很简单,几乎是线性的。
initGenes();var f = envaluateFitness(0)for(let i=0; i' + maxGene.join(','));
总结
以前一直觉得遗传算法很神秘,因此仔细研究了一下,觉得算法的效果很大程度上取决于各种参数的设定。另外,也许进化过程中出现过最优值,但最后的种群中也不一定会有最优值存在。真是能用其它方法可以解决时最好不用它,呵呵!