引言算法
進化計算(Evolutionary Computation)這個涵蓋的範圍比較廣,其中包括基因算法(Genetic Algorithm)、進化式策略(Evolutionary Strategy)、基因程序(Genetic Programming)等等。這篇是進化計算的開篇,我會從基因算法入手,進而介紹進化計算中的一些基本思想。學習
基因算法與A*、Tabu、BFS等一些啓發式算法,最大的不一樣即是:從針對個體,轉變到針對由個體組成的「羣體」(Population)。根據適應值(Fitness)來決定個體的優秀程度。code
每一次操做,從羣體中挑選兩個優秀的個體,取出這兩個個體的基因,進行拆分重組,從而獲得新方案,放入新一代的羣體中去。ci
其中利用到了生物學中的重組(Recombination)、選擇(Selection)和突變(Mutation)。因此在學習這一算法的時候,不妨和生物學中一些概念進行類比,這樣可以更好地理解基因算法的工做原理。資源
基因算法,在挑選的過程當中,隨機地挑選了兩個優秀個體;在對兩個個體的基因重組的時候,也引用了突變這一個不肯定因素,整個過程貌似都籠罩在「隨機」陰影下。確實在某種意義上,基因算法是一種隨機搜索的算法。但必須指出的是基因算法在搜索能力上大大優於普通的隨機搜索。it
接下來介紹一些經常使用的基因重組算法。io
通常來講,包括這三個操做:交叉(Crossover)、突變Mutation和倒置(Inversion)。原理
接下來我解釋這三個操做:select
交叉
ParentA(1111111111111111) ParentB(0000000000000000)搜索
Crossover
ChildA(1111111100000000) ChildB(0000000011111111)
就像上面,將ParentA切成兩段,同時也將ParentB的基因鏈也切成兩段。
先將ParentB的一半基因連接在ParentA的一半基因鏈後面,從而產生ChildA;同理可得ChildB,只是交換了ParentA和ParentB的順序。
突變
ParentA(1111111111111111) ParentB(0000000000000000) Mutation ChildA(1111111101111111) ChildB(0000000000100000)
這裏ChildA從ParentA中獲得了所有的基因,可是能夠發現,其中ChildA中有一位是0,而0顯然不是ParentA的基因,因此這即是突變。
同理可得ChildB。
倒置
ParentA(1111111111111111) ParentB(0000000000000000) Inversion ChildA(1111111100011111) ChildB(0000000111100000)
這個操做的結果有點相似突變。其實這個的操做過程是這樣的:去ParentA的一個片斷,對這個片斷中每個基因進行取反,從而獲得了ChildA。
基因算法的流程也是簡單易懂的,接下來就大體描述一下這個流程:
首先即是建立羣體,不斷隨機建立個體。
從當前羣體中挑選兩個優秀個體,對其基因進行重組,生成兩個新個體,這兩個新個體便組成了新一代的羣體。
利用步驟2,建立一個與當前羣體容量至關的新一代羣體,便將新一代羣體設定爲當前羣體。
判斷是否獲得了咱們所須要的個體,若是獲得就中止算法。
判斷羣體是否再也不符合要求(失去了多樣性),若是不符合就中止算法;若是符合就繼續步驟3。
僞代碼:
Population[2][NUM]; //隨機得到NUM個個體,並放入羣體Population[0]中去 Rand(Population[0]); curGeneration = 0; While(TRUE) { newGeneration = curGeneration == 1 ? 0 : 1; For(i = 0 ; i < NUM ; ++i) { //select : 從當前羣體Population中挑選優秀的個體做爲這次操做的父輩 ParantA = Select(Population[curGeneration]); ParentB = Select(Population[curGeneration]); //Recombination: 重組ParentA和ParentB的基因鏈,得到新個體Child Child = Recombination(ParentA,ParentB); //計算新一代個體的適應值 CalculateFitness(Child); //若是得到Child並不比父輩的優秀,從新重組 If(Child.Fitness< ParentA.Fitness || Child.Fitness < ParentB.Fitness) { --i; Continue; } //將新個體加入新一代的羣體中 Population[newGeneration][i]= Child; } //當羣體之間的個體差別很小的時候,考慮退出算法 If(avgfitness / maxfitness > 0.99999) Break; curGeneration = newGeneration; }
這裏舉TSP問題,TSP問題在另外一篇文章《AI中的幾種搜索算法---SA搜索算法》有提到過,那個時候主要用的是SA算法對這個問題進行了求解。
這裏咱們會用基因算法,再次求解這個問題。雖然已經介紹過TSP問題,這裏爲了閱讀的方便,我就直接拷貝了《AI中的幾種搜索算法---SA搜索算法》的部份內容。
TSP問題即旅行商問題:一個旅行商A被分配到一個任務,公司要求A去幾個城市進行公司業務拓展,因此A就會拿出地圖制定一個合理的路線。其中路線的要求即是消耗最小,而且可以從某一個城市出發,而且最後返回該城市時,已經訪問過了全部城市。
這裏咱們能夠計算出整個路線的路程。而這個路程和咱們以前提到過的Fitness成反比,路程越長,表示這個路線越差。
對於這個公式,我稍做解釋:Fitness就是咱們一直提到的適應能力(適應值),Length(solution)計算路線solution的路程長度。
這裏咱們介紹一個新的基因重組算法。較之於以前介紹的「交叉」、「突變」和「倒置」,這個算法會複雜一點。
雜交算子,算法來於《構建「基因庫」求解TSP問題的混合遺傳算法》
接下來開始介紹:
首先從當前羣體中,隨機選取兩個優秀的個體做爲父輩:ParentA和ParentB。
隨機選取兩個基因位置(即處於基因鏈中第幾個位置):PosA1和PosA2。
找到ParentA基因鏈,PosA1和PosA2位置處的基因:G1和G2。
找到ParentB基因鏈中基因G1和G2所處的位置:PosB1和PosB2。
將ParentB基因鏈中,與處於ParentA基因鏈PosA1和PosA2之間相同的基因去掉。
而後若是PosB1 < PosB2,將處於ParentA基因鏈PosA1和PosA2之間的基因片斷,在ParentB基因鏈的PosB1位置開始順序插入;
若是PosB1 >= PosB2,將將處於ParentA基因鏈PosA1和PosA2之間的基因片斷,在ParentB基因鏈的PosB2位置開始逆序插入。
下面舉一個具體的例子
隨機取PosA1 = 3 , PosA2 = 6 , 獲得基因G1 = 3 , G2 = 6 在ParentB基因鏈的位置PosB1 = 8 , PosB2 = 5 ParentA : 1 2 3 4 5 6 7 8 9 ParentB: 2 4 7 8 6 5 1 3 9 將ParentB基因鏈,去除基因 3,4,5,6 獲得: ParentB: 2 x 7 8 x x 1 x 9 由於PosB1 > PosB2,因此在PosB2處開始逆序插入(3,4,5,6) 獲得: Child: 2 7 8 6 5 4 3 1 9
若是想要詳細瞭解能夠去基因算法解決TSP問題處下載
下面是整個基因算法的流程代碼:
int tsp_ga(City * cities,intnCities,int ** path) { srand(time(NULL));// *path = new int[nCities]; floatsumCurFitness ; //初始化羣體,隨機獲得一羣個體 InitPopulation(cities,nCities,sumCurFitness); intcurGeneration = 0,generation = 0; int iBest =0; for(;1 ;++generation)//循環 { int newGeneration = curGeneration == 0 ? 1 : 0; float sumFitness = 0.0 , maxFitness = 0.0; //開始得到新個體 for (int i = 0 ; i< NUMPOPULATION ; ++i) { //選取優秀個體做爲父輩 int parenta =SelectParent(curGeneration,sumCurFitness); int parentb =SelectParent(curGeneration,sumCurFitness); //開始重組父輩的基因鏈,得到新個體 //這裏使用上面介紹的雜交算法 Recombination(population[curGeneration][parenta],population[curGeneration][parentb], population[newGeneration][i],nCities); //計算新個體的fitness CaculateFitness(cities,nCities,population[newGeneration][i]); float fitness= population[newGeneration][i].fitness; //若是新個體並無比父輩優秀,從新產生新個體 if(fitness< population[curGeneration][parenta].fitness || fitness <population[curGeneration][parentb].fitness) { --i; continue; } if(fitness> maxFitness) { maxFitness = fitness; iBest = i; } sumFitness += fitness; } sumCurFitness = sumFitness; curGeneration = newGeneration; float result = sumFitness / (maxFitness*NUMPOPULATION ); if( result > 0.99999)//若是羣體中個體差別很小,中止算法 break; } for (int i = 0 ; i < nCities ; ++i) { (*path)[i] =population[curGeneration][iBest].path[i]; } //清理資源 for (int i = 0; i < NUMPOPULATION ; ++i) { delete[]population[0][i].path; delete[]population[1][i].path; } returngeneration; } //下面就是基因鏈重組算法的實現代碼
void Recombination(constIndividual & parenta,const Individual &parentb,Individual & child,int n) { int posa1 =rand()%(n-1); int posa2 =rand()%n; while(posa2<= posa1) posa2 = rand()%n; // int lseg =posa2 - posa1 + 1; int * genseg= new int[lseg]; for(int i = posa1 ; i <= posa2 ; ++i) genseg[i-posa1] = parenta.path[i]; //// int posb1 =-1,posb2 = -1; int iChild =0 , iStartInsert = 0; for(int i = 0 ; i < n ; ++i) { int gb =parentb.path[i]; int j =0; for(; j< lseg; ++j) { intg = genseg[j]; if(gb== g) { if(0== j) { posb1 = i; iStartInsert = iChild; iChild += lseg; } elseif(j == (lseg-1)) posb2 = i; break; } } // if(lseg== j) { child.path[iChild++] = gb; } } if(posb1< posb2) for(int i = 0 ; i < lseg ; ++i) child.path[i + iStartInsert] =genseg[i]; else for(int i = 0 ; i < lseg ; ++i) child.path[i + iStartInsert] =genseg[lseg - i -1]; delete[]genseg; }
基因算法總的來講體現了一個「優勝劣汰」的法則,優秀的基因存活下來。並且基因算法從針對於個體轉到了羣體,有別於A*這些普通的啓發式算法。
其中我在實現這個算法的時候,在嘗試基因鏈重組算法的時候,一直沒有找到一個可以保證優秀基因遺傳下去的好方法,所在在網上搜了一下關於TSP和基因算法,找到了一篇論文《構建「基因庫」求解TSP問題的混合遺傳算法》,有興趣的讀者能夠去看一下這篇文章。
若是有興趣的能夠留言,一塊兒交流一下算法學習的心得。