基於遺傳算法(Genetic Algorithm)的TSP問題求解(C)


 

基於遺傳算法的TSP問題求解(C)

 

  TSP問題:

  TSP(Travelling salesman problem): 譯作“旅行商問題”, 一個商人由於業務的需要,要到n個城市,每個城市之間都有一條路徑和其他所有的城市相連。現在要求從一個城市出發,穿越所有其他所有的城市,再回到出發的城市。 出於成本的考慮,要求商人走的路徑的長短最短。問能否找到這樣的一條路徑?

  這是個經典的NP-complete問題。 時間復雜度為θ(n!)。 隨着城市的數量規模增大,在有限的時間內得不到問題的最優解。 我們只能退而求其次,在有限的時間內(或可接受的時間內)找到一個近似最優解,而且這個近似最優解和最優解的誤差我們也是可以接受的。 出於這樣的考慮,為求解這類問題的啟發式,元啟發式算法,演化算法營運而生。

  隨着研究的深入,TSP問題已經演化成好多版本。本文的C程序對於對稱和非對稱的版本都適用。

  遺傳算法:

  遺傳算法(Genetic Algorithm),也稱進化算法,是依據生物進化的過程而提出的一種啟發式算法,由美國的J.Holland於1975年首次提出。其主要特點是依據生物進化中的“優勝劣汰”或者“競爭”法作為問題的解的選擇依據。 直接對結構對象進行操作,不存在求導和函數連續性的限定;具有內在的隱並行性和更好的全局尋優能力;采用概率化的尋優方法,能自動獲取和指導優化的搜索空間,自適應地調整搜索方向,不需要確定的規則。遺傳算法的這些性質,已被人們廣泛地應用於組合優化、機器學習、信號處理、自適應控制和人工生命等領域。它是現代有關智能計算中的關鍵技術之一

  遺傳算法的基本原理:

  首先根據問題產生多個初始可行解(),然后從初始解中選擇諾干優異的個體(問題的解)進行各種操作(交叉,變異)用以產生新的后代。再從新的后代中選擇優異的個體進行相應的操作產生它們的后代,如此不斷循環,直到迭代的次數達到了預先設定的值或者多次迭代以后產生的最優異的個體(最優解)的質量並沒有明顯的提高,就可以停止迭代,這時的最優個體(最優解)最為問題的解。

  從上面的原理我們可以知曉該算法主要涉及的步驟如圖 1所示:

  • 編碼  

  在解實現初始化之前,如何表示一個解即編碼是一個很重要的問題。 一般的編碼方式有:

  1. 基於二進制編碼: 作為遺傳算法傳統的編碼方式。對於TSP問題,經過交叉后可能得不到可行解,需要修補操作。
  2. 基於矩陣的編碼: 需要更多的額外空間,而且隨着種群規模的增加,存儲空間劇增和前期處理工作任務很龐大。后續操作也比較復雜。
  3. 基於鄰近的編碼: 采用鏈表的方式存儲,但是變異,交叉操作復雜
  4. 基於格雷碼方法:傳統二進制編碼的一種改進,容易實現交叉,變異操作,但是對於該問題不是最優的
  5. 基於符號編碼:對於TSP問題,我們直接使用路徑來表示一個染色體。即使用一個整數數組,數組長度為TSP城市的數量,數組中存儲各個城市編號,從下標為0開始逐個遍歷數組元素形成的一個序列即為路徑(對於要回到原點的要求,為了方便表示,在這里不作考慮,只是在計算路徑長度的時候會添加相應的長度)。
  • 形成初始解

  采用隨機的方式產生諾干個(種群規模)的序列,即產生符合城市編號的隨機數存儲在數組中,數組中的元素包含所有的城市編號,而且沒有重復的編碼。數組的個數為種群規模。

  • 選擇

  在形成一定數量的可行解(染色體)后,需要對這些父代的染色體進行篩選。根據生物遺傳的基因的優勝劣汰原則,在篩選染色體的我們也會秉承精英保留原則,使得優異的基因有更多的機會得到遺傳。

  適應度函數: 這里我們選擇路徑長度的倒數來作為解的適應度

  在這個問題中,我們選擇“輪盤賭”算法來篩選染色體。

  基本原理:計算每個染色體(路徑)的長度的倒數,再得到所有路徑倒數之和,用每條路徑的倒數除以所有所有路徑倒數之和即為這條路徑被選中的概率(路徑越短,概率越高)。

  • 交叉 

  這里我們選擇交替位置交叉法(Alternating Position Crossover,APX)來對一對染色體進行交叉操作,其基本原理如下圖所示

  左邊為父代的兩個染色體,右邊為子代染色體。 將左上的數組第一個元素放入右上數組的第一位置中,再轉移到左下數組第一個元素,查看右上數組是否已經包含了該元素,如果未包含將其插入右上數組中,否則插入右下數組中。接着從左上數組的第二個元素開始,到左下第二個元素,和前次同意的判斷操作。如此類推直到右邊兩個數組都被填滿了為止。

  交叉概率:交叉概率對於解的收斂速度有着重要的影響。一般選擇0.6-1。

  • 變異

  生物的進化,除了遺傳父母的基因,還有自身基因有一定的概率突變。基於這個原理,變異操作在一定的概率上是作用於染色體自身的。

  變異概率:一定的概率師兄自身基因的改變

  在這個問題中,我們選擇位置倒換法,即染色體上隨機的產生兩個位置上數值互換。

  • 終止條件

  一般有兩種方式停止交叉,變異的操作。一,預先設定迭代次數。二,多次跌代后,解的質量得不到一定要求的提高,或者解達到要求的質量,這時都可以停止迭代。這個問題上我們選擇第一種。

  

  基於TSP問題的遺傳算法代碼如下:

  

  1 #include <stdio.h>
  2 #include <tchar.h>
  3 #include <math.h>
  4 #include <stdlib.h>
  5 #include <time.h>
  6 
  7 
  8 int scale; /* 種群規模 */
  9 int cityNum; /* 城市數量 */
 10 int *pathlength; /* 存儲種群每個個體路徑長度 */
 11 double *cumPropa; /* 存儲個體累積概率 */
 12 int bestlength; /* 最佳路徑長度 */
 13 int *bestpath; /* 最佳路徑 */
 14 float pc; /* 交叉概率 */
 15 float pm; /* 變異概率 */
 16 int count; /* 變異次數 */
 17 int MAX_Gene; /* 迭代次數 */
 18 
 19 /* 函數聲明 */
 20 void APCrossover(int **,int ,int ,int );
 21 void copy(int *,int **,int ,int );
 22 void cumDistance(int **,int **, int *,int ,int );
 23 void cumulatePro(int **,double *,int *,int );
 24 void copytoBestPath(int *,int **,int ,int );
 25 void copy(int *,int **,int ,int );
 26 int *creatArray1(int );
 27 double *creatArraydoub(int );
 28 int **creatArray2(int , int );
 29 void CrossAndMutation(int **,int );
 30 void cumDistance(int **,int **, int *,int ,int);
 31 void mutation(int **,int);
 32 void Initialize(int **, int , int );
 33 void readfile(int **, int , int );
 34 void rouletteAlgo(int **,double *, int **,int );
 35 void NcopyO(int **,int **);
 36 
 37 //創建一個整形的二維整數數組
 38 int **creatArray2(int scale, int cityNum)
 39 {
 40     int i;
 41 
 42     int **ptemp;
 43 
 44     ptemp=(int **)malloc(sizeof(int *)*scale);
 45         for(i=0;i<scale;i++)
 46             ptemp[i]=(int *)malloc(sizeof(int)*cityNum);
 47 
 48     return ptemp;
 49 }
 50 //創建一個整形的一維數組
 51 int *creatArray1(int scale)
 52 {
 53     int *tempcreate;
 54     tempcreate=(int *)malloc(sizeof(int)*scale);
 55     return tempcreate;
 56 }
 57 //創建一個雙精度型的一維數組
 58 double *creatArraydoub(int scale)
 59 {
 60     double *tempcreate;
 61     tempcreate=(double *)malloc(sizeof(double)*scale);
 62     return tempcreate;
 63 }
 64 
 65 // 二維數組拷貝
 66 void NcopyO(int **newgeneration,int **oldgeneratoin)
 67 {
 68     int i,j;
 69     for(i=0;i<scale;i++)
 70         for(j=0;j<cityNum;j++)
 71             oldgeneratoin[i][j]=newgeneration[i][j];
 72             
 73 }
 74 
 75 
 76 
 77 //計算一次迭代各可行解的路徑長度
 78 void cumDistance(int **oldgeneration,int **cityDistance, int *pathlength,int cityNum,int scale)
 79 {
 80     int i,j,k;
 81     int length=0;
 82     int min=88888;
 83     int minp;
 84 
 85     for(i=0;i<scale;i++)
 86     {
 87         length=0;
 88         for(k=0;k<cityNum-1;k++)
 89         {
 90             j=k+1;
 91             length+=cityDistance[oldgeneration[i][k]][oldgeneration[i][j]];
 92         }
 93         //題目要求回到原點,所以加上回到原點的距離
 94         pathlength[i]=length+cityDistance[oldgeneration[i][cityNum-1]][oldgeneration[i][0]];
 95 
 96         if(pathlength[i]<min)
 97         {
 98             min=pathlength[i];
 99             minp=i;
100         }
101     }
102     if(min<bestlength)
103     {
104         bestlength=min;
105         //將每代最優解直接加入下一代中,即“精英保留”原則
106         copytoBestPath(bestpath,oldgeneration,minp,cityNum);
107     }
108 
109 }
110 
111 
112 
113 
114 void copytoBestPath(int *bestpath,int **oldGeneration,int position,int cityNum)
115 {
116     int i;
117     for(i=0;i<cityNum;i++)
118         bestpath[i]=oldGeneration[position][i];
119 }
120 
121 
122 
123 //計算一次迭代中各個可行解作為交叉操作的累積概率
124 
125 void cumulatePro(int **generation,double *cumPropa,int *pathlength,int scale)
126 {
127     int i;
128     double sumlength=0;
129 
130     for(i=0;i<scale;i++)
131         sumlength+=1/(double)pathlength[i];
132 
133     cumPropa[0]=(1/(double)pathlength[0])/sumlength;
134 
135     for(i=1;i<scale;i++)
136     {
137         cumPropa[i]=(1/(double)pathlength[i])/sumlength+cumPropa[i-1];
138     }
139 }
140 
141 
142 //初始化話生產相應規模的可行解
143 void Initialize(int **geneSolution, int scale, int cityNum)
144 {
145     int i,j,k;
146     int randnum;
147     bool exist;
148 
149     srand(time(NULL));
150 
151     for(i=0;i<scale;i++)
152         for(j=0;j<cityNum;j++)
153         {
154             exist=true;
155             while(exist==true){
156             //注意:rand()/Rand_MAX 結果只能是0, 應該先進行類型轉換
157             randnum=(int)(((double)rand()/RAND_MAX)*cityNum);
158                 for(k=0;k<j;k++)
159                     if(geneSolution[i][k]==randnum)
160                         break;
161                 if(k==j)
162                     exist=false;
163             }
164             geneSolution[i][j]=randnum;
165         }
166 
167 }
168 
169 //讀取文件中的各相鄰點的距離信息
170 void readfile(int **cityDistance, int scale, int cityNum)
171 {
172     int i,j;
173     FILE *fp;
174     errno_t err;
175 
176     err=fopen_s(&fp,"data.txt","r");
177 
178     if(err!=0)
179     {
180         printf("The file can not be found!\n");
181     }
182     else
183     {
184         
185         for(i=0;i<scale;i++)
186         {
187             for(j=0;j<cityNum;j++)
188             {        
189                 fscanf_s(fp,"%d ",&cityDistance[i][j]);
190             }
191         }
192         
193 
194 
195         fclose(fp);
196     }
197 
198 }
199 
200 //拷貝一條路徑
201 void copy(int *oldGeneration,int **newGeneration,int position,int cityNum)
202 {
203     int i;
204     for(i=0;i<cityNum;i++)
205         newGeneration[position][i]=oldGeneration[i];
206 }
207 
208 //使用輪盤賭算法選擇交叉的對象
209 void rouletteAlgo(int **oldGeneration,double *cumPropa, int **newGeneration,int scale)
210 {
211     int i,j;
212     double randNum;
213 
214     for(i=0;i<scale-1;i++)
215     {
216         randNum=(double)rand()/RAND_MAX;
217         if(cumPropa[0]>=randNum)
218             copy(oldGeneration[0],newGeneration,i,scale);
219         else
220         {
221             for(j=0;j<scale;j++)
222                 if(randNum>cumPropa[j] && randNum<=cumPropa[j])
223                             break;
224             copy(oldGeneration[i],newGeneration,i,scale);
225         }
226     }
227 
228     copy(bestpath,newGeneration,scale-1,scale);
229 
230 }
231 
232 //交叉操作:交替位置交叉法(Alternating Position Crossover,APX)
233 void APCrossover(int **newgeneration,int p1,int p2,int cityNum)
234 {
235     int i;
236     int s1=1;
237     int s2=0;
238 
239     int *tempArray1;
240     int *tempArray2;
241 
242     tempArray1=creatArray1(cityNum);
243     tempArray2=creatArray1(cityNum);
244 
245     for(i=0;i<cityNum;i++)
246     {
247         tempArray1[i]=newgeneration[p1][i];
248         tempArray2[i]=newgeneration[p2][i];
249     }
250 
251     int m,n;
252     m=1;
253     n=0;
254     
255     while(s1<10 || s2<10)
256     {
257         for(i=0;i<s1;i++)
258             if(tempArray1[m]==newgeneration[p1][i])
259                 break;
260         if(i==s1)
261         {
262             newgeneration[p1][s1]=tempArray1[m];
263             m++;
264             s1++;
265         }
266         else{
267             newgeneration[p2][s2]=tempArray1[m];
268             m++;
269             s2++;
270         }
271 
272 
273         for(i=0;i<s1;i++)
274             if(tempArray2[n]==newgeneration[p2][i])
275                 break;
276         if(i==s1)
277         {
278             newgeneration[p1][s1]=tempArray2[n];
279             n++;
280             s1++;
281         }
282         else{
283             newgeneration[p2][s2]=tempArray2[n];
284             n++;
285             s2++;
286         }
287     }
288 
289 }
290 
291 //變異操作
292 void mutation(int **newgeneration,int p1)
293 {
294     int rand1,rand2;
295     int temp;
296     int i;
297 
298     srand(time(NULL));
299 
300     for(i=0;i<count;i++)
301     {
302 
303         rand1=(int)(((double)rand()/RAND_MAX)*cityNum);
304         rand2=(int)(((double)rand()/RAND_MAX)*cityNum);
305         while(rand1==rand2)
306         {
307             rand2=(int)(((double)rand()/RAND_MAX)*cityNum);
308         }
309 
310         temp=newgeneration[p1][rand1];
311         newgeneration[p1][rand1]=newgeneration[p1][rand2];
312         newgeneration[p1][rand2]=temp;
313     }
314 
315 }
316 
317 //對一代群體進行交叉變異操作
318 void CrossAndMutation(int **newgeneration,int cityNum)
319 {
320     float rand1,rand2;
321     int k;
322     for(k=0;k<cityNum;k=k+2)
323     {
324         srand(time(NULL));
325         rand1=(float)rand()/RAND_MAX;
326 
327         if(rand1>pc)
328         {
329             APCrossover(newgeneration,k,k+1,cityNum);
330         }
331         else
332         {
333             rand2=(float)rand()/RAND_MAX;
334             if(rand2>pm)
335             {
336                 mutation(newgeneration,k);
337             }
338             rand2=(float)rand()/RAND_MAX;
339             if(rand2>pm)
340             {
341                 mutation(newgeneration,k+1);
342             }
343 
344         }
345     }
346 
347 }
348 
349 int main()
350 {
351     int **a;
352     int **oldGeneration;
353     int **newGeneration;
354     int i,j;
355     
356     MAX_Gene=20;
357     cityNum=10;
358     scale=10;
359     bestlength=888888;
360     pc=0.6;
361     pm=0.5;
362     count=4;
363     int m;
364 
365 
366     a=creatArray2(scale,cityNum);
367     pathlength=creatArray1(scale);
368     cumPropa=creatArraydoub(scale);
369     bestpath=creatArray1(cityNum);
370 
371     readfile(a,scale,cityNum);
372 
373     oldGeneration=creatArray2(scale,cityNum);
374     newGeneration=creatArray2(scale,cityNum);
375 
376     Initialize(newGeneration,scale,cityNum);
377 
378     for(m=0;m<MAX_Gene;m++)
379     {
380         NcopyO(newGeneration,oldGeneration);
381         cumDistance(oldGeneration,a,pathlength,scale,cityNum);
382         cumulatePro(oldGeneration,cumPropa,pathlength,scale);
383         rouletteAlgo(oldGeneration,cumPropa,newGeneration,scale);
384         CrossAndMutation(newGeneration,cityNum);
385 
386     }
387 
388     printf("The best path is :\n");
389 
390 
391     for(i=0;i<cityNum;i++)
392     {
393         printf("%d  ",bestpath[i]);
394     }
395     printf("\n");
396     
397     printf("The minmum length is %d\n",bestlength);
398     
399     return 0;
400 }

   遺傳算法只能得到問題的近似最優解,而且對不同的問題,該算法的性能也不一樣。一般要求結合問題的一些特點和屬性或者與其他的演化算法相結合,例如蟻群算法,粒子群算法,模擬退火法等。

  

  

  

 

 

  

  

  

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM