遺傳算法解決旅行商問題(TSP)


這次的文章是以一份報告的形式貼上來,代碼只是簡單實現,難免有漏洞,比如循環輸入的控制條件,說是要求輸入1,只要輸入非0就行。希望會幫到以后的同學(*^-^*)

一、問題描述

   旅行商問題(Traveling-Salesman Problem,TSP)。設有n個互相可直達的城市,某推銷商准備從其中的A城出發,周游各城市一遍,最后又回到A城。要求為該旅行商規划一條最短的旅行路線。

二、目的

   為了解決旅行商問題,用了遺傳算法,模擬染色體的遺傳過程,進行求解。

   為了直觀的更有比較性的觀察到程序的運行效果,我這里程序里給定了10個城市的坐標,並計算出其任意兩個的歐氏距離,10個點的位置排布見圖1。程序的理想最優距離為20.485281,即繞三角形一圈,而且路程起點不固定,因為只要滿足點圍着三角形一圈即為最短距離,最優解。所以問題轉換為,求圖中10 個點的不重復點的閉環序列的距離最小值。

                   

                                       圖 1

三、原理

1、內部變量介紹 

程序總體圍繞了遺傳算法的三個主要步驟:選擇--復制,交叉,變異。給定了10個種群,即10條染色體,每條染色體都是除首位外不重復的點組成,首尾相同保證路線是閉合的,所以一條染色體包含11個點。

種群由一個結構體group表示,內含城市的序列int city[11]、種群的適應度double fit、該種群適應度占總群體適應度的比例double p,和為了應用賭輪選擇機制的積累概率 double jlleigailv。

程序還包括一個始終記錄所有種群中的最優解的城市序列數組groupbest[11],記錄最優解的適應度,即最大適應度的變量 double groupbestfit。

種群的最大繁衍代數設置為1000,用戶能夠輸入繁衍代數,但必須在1000以內。10個點的不同排列序列有10!種,即3628800中排列可能,其中各代之間可能產生重復,不同種群間也會出現重復,學生覺得1000左右應該能驗證程序的性能了,就定為1000。

2、運行思想介紹

(a)采用整數編碼的方式,標記0到9號城市。

(b)選擇--復制:

利用賭輪選擇機制,分10次從10個種群中挑選出10個染色體進行復制。

每次隨機生成一個0到1之內的小數,因為適應度越高的染色體的積累概率區間越大,所以適應度越高的染色體被選擇的次數會越多,滿足了優勝劣汰的原則。

選擇--復制完后,要重新計算每個種群的適應度等信息,與已經保存的最優染色體進行比較,如果比已經存在的適應度還要高就進行最優染色體的更新。如果最優染色體沒有更新,則說明新成的最大適應度種群不如以前的好,則在新生成的種群中找到適應度最低的,用最優染色體提換掉。

(c)交叉:

    每一代的繁衍都讓10個種群中相鄰的兩個種群進行染色體交叉,交叉率為1,即0號種群和1號種群交叉,2號種群和3號種群交叉,以此類推。交叉段是由2個隨機數決定,采用部分映射交叉,直接交換由隨機數產生的染色體片段。

交叉完后因為要滿足點的不重復,所以要進行消除重復的操作。原理是用一個數組保留交叉過來的染色體片段,刪除染色體上已經交換的片段,在剩下的點中消除與新交換片段中重復的點,然后將原染色體剩下的點都向前移集中在頭部,再將保存在數組中的新交換過來的染色體插入到頭部之后,在以上過程中用一個數組記錄已經存在的點。接下來將沒有用的點順序插到染色體尾部,到此已經生成了新的染色體。

交叉完后要重新計算每個種群的適應度等信息,與已經保存的最優染色體進行比較,如果比已經存在的適應度還要高就進行最優染色體的更新。如果最優染色體沒有更新,則說明新成的最大適應度種群不如以前的好,則在新生成的種群中找到適應度最低的,用最優染色體提換掉。

(d)變異:

因為變異在自然界並不是每次都會發生,所有每次要盡行變異前都生成一個0到9內的隨機整數,如果大於3就進行變異,否則不變異,總體變異率為0.6。因為這個染色體和自然中的每一段都對應一個功能的染色體不一樣,染色體越是按數的相鄰大小排列,距離會越短,所以變異就起了很大作用,起到調整點的順序的作用,所以變異率要大一點。

如果要變異,則變異3次。每次生成2個隨機數,決定要在哪個種群變異哪一個位置。比如挑選了第二條染色體,變異第3個位置,則將染色體的3號位和6號位互換,即互換位置和為9。

(e)輸出:

輸出用戶程序得到的最優解的城市序列、路程距離、適應度和在第幾代得到的最優解。

程序可以循環多次運行,只要用戶按照提示輸入。

四、結果

    程序的總體結果又很大隨機性,但特別壞的結果畢竟只占少數,多數都是一般結果,多隨機運行幾次,還有控制繁衍的代數就能夠得到比較好的結果。令人振奮的是,經過多次的嘗試,又一次輸出了理想最優解,正好是圍三角形繞一圈,路程距離是20.485281,在第756代生成。一下是程序運行的比較好的結果、比較差的結果的舉例,和最優結果的貼圖,圖2為最優結果。

圖2

圖3

圖4

五、討論

這次程序--遺傳算法解決旅行商問題,的總體思想結合了課本和網上有關內容,但令人振奮的是,上圖1給的實例,還有具體的實現代碼和里面的具體思想都是出自學生仔細思考的結果,沒有抄襲,程序的每一步正確運行都凝聚了學生認真對待的心血。

寫這個程序總體上分成了2個步驟,思想理解與構造,具體代碼實現。

大約用了1個白天天左右的時間去了解:遺傳算法怎么去解決這個問題,怎樣進行選擇復制,交叉的方法有哪些,變異的方法又有哪些。決定了每一步用的思想后便進行具體敲代碼。

敲代碼又分成了兩段。第一段時間,在開始編碼的那天晚上我完成了程序的基本內容,跑一下也沒有問題,只是效果有很大隨機性,結果不是很好,第二天想了很久,又看了很多資料發現,原因在於交叉選擇的方法不好。一開始那種方法是,交換后的染色體直接放到相鄰染色體的相應位置,在其他地方進行消除重復的操作,染色體上會出現很多空洞,然后用沒有用過的點順序插在空洞里,構成新的染色體。后來我想了很久,吃飯時走在路上一想,不對啊,這樣有可能完全破壞了原來的優秀隊列,因為這個優秀染色體就是由排列順序決定的。回來后就修改了交叉的算法,形成了現在的算法,這樣能夠最大程度的保留原來的順序,又能很大可能獲得新的優秀隊列。

程序有400多行,交叉算法用的變量多而且又復雜,其中用來找bug的時間比較多,敲代碼的時候有很多細節問題腦子一開始想好了的,沒有敲上去。而且一邊敲,一邊也在對原由思想進行改進。

總之,遺傳算法的可移植性很大,可以用來逼近很多問題的最優解,實在是很厲害。做了這么多,我真的感覺收獲良多,一些細節處的bug好煩人,希望以后自己能夠更加細心。

六、代碼

里面的思想在前面已經講了,但里面的變量我設的時候標注的不是很清楚,思想懂了,代碼完全可以自己寫出來的(*^-^*)

  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<time.h>
  4 #include<math.h>
  5 
  6 double distance[10][11];//城市之間的距離 
  7 int dai,die;
  8 int cities[2][10];//記錄城市坐標
  9 int citynum=10;
 10 int groupbest[11];//最優解染色體 
 11 double groupbestp;//最優解的p 
 12 double groupbestfit;//最優解的fit 
 13 int changebest;//要不要用最優解替代新種群 
 14 
 15 struct group
 16 {
 17   int city[11];//一維記錄城市序號,二三維記錄坐標
 18   double p;//占總群的概率 
 19   double fit;//適應度 
 20   double jileigailv;
 21 }group[10];
 22 
 23 /*用來計算種群的p、fit*/
 24 void jisuan() 
 25 {
 26   int i,j,k;
 27   double ss,s;
 28   s=0.0;
 29   ss=0.0;
 30   for(k=0;k<10;k++)
 31   {
 32     for(i=0;i<citynum;i++)
 33         {
 34           s+=distance[group[k].city[i]][group[k].city[i+1]];
 35         }
 36     group[k].fit=1.0/s;
 37     ss+=group[k].fit;
 38   }
 39   s=0.0;
 40   for(i=0;i<10;i++)
 41   {
 42   group[i].p=group[i].fit/ss;
 43   s+=group[i].p;
 44   group[i].jileigailv=s;
 45   } 
 46 }
 47 /*保存最優解*/ 
 48 void savebest()
 49 {
 50   int i,j,flag=0;
 51   double fit=groupbestfit;
 52   j=0;
 53   for(i=0;i<10;i++)
 54   {
 55      if(group[i].fit>fit)
 56      {
 57        j=i;
 58        fit=group[i].fit;
 59        flag=1;//標記已經有更好的 
 60      }
 61   }
 62      if(flag)
 63      {
 64          dai=die;
 65        for(i=0;i<citynum+1;i++)//保存最優解
 66        {
 67           groupbest[i]=group[j].city[i];
 68        }
 69        groupbestp=group[j].p;
 70        changebest=0; 
 71        groupbestfit=group[j].fit;
 72      }
 73      else
 74       changebest=1;//說明新生成的解還不如原來的好,要進行替換
 75 }
 76 /*用最優解替代新種群中的最差的染色體*/
 77 void changebestgroup()
 78 {
 79   int j,i;
 80   double fit=group[0].fit;
 81   j=0;
 82   if(changebest)
 83   {
 84     for(i=1;i<10;i++)
 85     {
 86       if(group[i].fit<fit)
 87       {
 88         fit=group[i].fit;
 89         j=i;
 90       }
 91     }
 92     for(i=0;i<citynum+1;i++)
 93     {
 94       group[j].city[i]=groupbest[i];
 95     }
 96     jisuan();
 97   }
 98 }
 99 /*初始種群和城市坐標,計算距離*/ 
100 void chushigroup()
101 {
102   int i,j,t,flag,k;
103   double ss;
104   cities[0][0]=0;//初始化坐標
105   cities[1][0]=0;
106   cities[0][1]=2;
107   cities[1][1]=0;
108   cities[0][2]=4;
109   cities[1][2]=0;
110   cities[0][3]=6;
111   cities[1][3]=0;
112   cities[0][4]=6;
113   cities[1][4]=2;
114   cities[0][5]=6;
115   cities[1][5]=4;
116   cities[0][6]=6;
117   cities[1][6]=6;
118   cities[0][7]=5;
119   cities[1][7]=5;
120   cities[0][8]=4;
121   cities[1][8]=4;
122   cities[0][9]=2;
123   cities[1][9]=2;
124 memset(groupbest,-1,sizeof(groupbest));
125 groupbestp=0.0;
126 groupbestfit=0.0;
127 changebest=0;
128   for(i=0;i<citynum;i++)
129     for(j=0;j<=i;j++)
130     {
131       if(j==i)
132           distance[i][j]=0.0;
133       else
134       {
135           distance[i][j]=sqrt(pow(cities[0][i]-cities[0][j],2.0)+pow(cities[1][i]-cities[1][j],2.0));//歐氏距離
136           distance[j][i]=distance[i][j];
137       }
138     }
139   printf("最優解的距離是:%f\n",distance[0][1]+distance[1][2]+distance[2][3]+distance[3][4]+distance[4][5]+distance[5][6]+distance[6][7]+distance[7][8]+distance[8][9]+distance[9][0]);
140   srand((unsigned)time(NULL));
141   ss=0;
142   for(k=0;k<10;k++)
143   {//一個數量為10的種群,和10 個城市環 
144     for(i=0;i<citynum;i++)
145     {
146         flag=1;
147         while(flag)
148         {
149           t=rand()%citynum;
150            for(j=0;j<i;j++)
151           {
152             if(group[k].city[j]==t)
153             {
154               break;
155             }
156           }
157             if(j==i)
158             {
159               group[k].city[i]=t;
160               flag=0;
161             }
162          }
163      }
164    group[k].city[10]= group[k].city[0];
165   }
166   //以上產生了10 個種群,分別有不重復的染色體
167 
168 jisuan();
169 savebest();
170 printf("初始種群為:\n");
171 for(i=0;i<10;i++)
172 {
173   for(j=0;j<citynum+1;j++)
174       printf("%d ",group[i].city[j]);
175   printf("||適應度:%f,占總群的概率:%f\n",group[i].fit,group[i].p);
176 }
177 }
178 /*選擇--復制*/ 
179 void xuanze()
180 {
181   int i,j,temp[10][11],k;
182   double t;
183   srand((unsigned)time(NULL));
184   for(i=0;i<10;i++) //選10條染色體出來復制,賭輪
185   {
186     t=rand()%10000*1.0/10000;
187     for(j=0;j<10;j++)
188     {
189         
190       if(t<=group[j].jileigailv)
191       {
192           for(k=0;k<citynum+1;k++)
193           {
194             temp[i][k]=group[j].city[k];
195           }
196           break; 
197       } 
198     }
199   }
200   //拷貝新種群
201   for(i=0;i<10;i++)
202     for(j=0;j<citynum+1;j++)
203     {
204       group[i].city[j]=temp[i][j];
205     }
206     jisuan();
207     savebest();
208     changebestgroup();
209 }
210 /*交叉*/ 
211 void jiaocha()
212 {
213   int point1,point2,temp,i,j,k,temp3[2][10],temp2[2][10],num,write;
214   srand((unsigned)time(NULL));
215   point1=rand()%10;
216   point2=rand()%10;
217   if(point1>point2)
218   {
219     temp=point1;
220     point1=point2;
221     point2=temp;
222   }
223   //交換,每2條交換
224  if(point1!=point2)
225  {
226   for(j=1;j<10;j=j+2)
227   {
228        memset(temp3,-1,sizeof(temp3));
229        memset(temp2,-1,sizeof(temp2));
230        k=0;
231     for(i=point1;i<=point2;i++)
232     {
233      temp2[0][k]=group[j].city[i];
234      temp2[1][k]=group[j-1].city[i];
235      temp3[0][temp2[0][k]]=1;//標記數字已經存在了
236      temp3[1][temp2[1][k]]=1;
237      k++;
238      group[j].city[i]=-1;
239      group[j-1].city[i]=-1;
240     }
241     num=point2-point1+1;//交換的位數 
242      //消重
243     for(k=0;k<point1;k++)
244       {
245         if(temp3[0][group[j-1].city[k]]==1)
246         {
247            group[j-1].city[k]=-1;
248         }
249         else
250            temp3[0][group[j-1].city[k]]=1;
251       } 
252       for(k=point2+1;k<citynum;k++)
253       {
254         if(temp3[0][group[j-1].city[k]]==1)
255         {
256            group[j-1].city[k]=-1;
257         }
258         else
259            temp3[0][group[j-1].city[k]]=1;
260       }
261       for(k=0;k<point1;k++)
262       {
263         if(temp3[1][group[j].city[k]]==1)
264         {
265            group[j].city[k]=-1;
266         }
267         else
268            temp3[1][group[j].city[k]]=1;
269       } 
270       for(k=point2+1;k<citynum;k++)
271       {
272         if(temp3[1][group[j].city[k]]==1)
273         {
274            group[j].city[k]=-1;
275         }
276         else
277            temp3[1][group[j].city[k]]=1;
278       }
279       write=0; 
280       for(i=0;i<10;i++)
281       {
282       while(write<10&&group[j-1].city[write]==-1)
283       {
284         write++;
285       }
286       if(write<10)
287       {
288         temp=group[j-1].city[i];
289         group[j-1].city[i]=group[j-1].city[write];
290         group[j-1].city[write]=temp;
291         write++;
292       }
293       else
294       {
295           write=0;
296       for(k=i;k<10;k++)
297       {
298           group[j-1].city[k]=temp2[0][write++];
299           if(write==num)
300           break;
301        }
302       break;
303       }
304     } 
305      write=0; 
306      for(i=0;i<10;i++)
307      {
308       while(write<10&&group[j].city[write]==-1)
309       {
310         write++;
311       }
312       if(write<10)
313       {
314         temp=group[j].city[i];
315         group[j].city[i]=group[j].city[write];
316         group[j].city[write]=temp;
317         write++;
318       }
319       else
320       {
321           write=0;
322         for(k=i;k<10;k++)
323         {
324           group[j].city[k]=temp2[1][write++];
325           if(write==num)
326               break;
327          }
328         break;
329       }
330     } 
331     k=0;
332     for(i=0;i<citynum;i++)
333     {
334        if(group[j-1].city[i]==-1)
335        {
336          while(temp3[0][k]==1&&k<10)
337          {
338            k++;
339          }
340          group[j-1].city[i]=k++;
341        }
342     }
343     k=0;
344     for(i=0;i<citynum;i++)
345     {
346        if(group[j].city[i]==-1)
347        {
348          while(temp3[1][k]==1&&k<10)
349          {
350            k++;
351          }
352          group[j].city[i]=k++;
353        }
354     }
355    group[j].city[10]=group[j].city[0];
356    group[j-1].city[10]=group[j-1].city[0];
357   }//end of j
358  jisuan();
359  savebest();
360  changebestgroup();
361  } //end of if(!)
362 }
363 /*變異*/ 
364 void bianyi()
365 {
366    int t1,t2,temp,t,s=3;
367    srand((unsigned)time(NULL));
368    t=rand()%10;
369    if(t>3)//變異率為3/5 
370    {
371        //挑1個不同的變異,只交換一位
372     t1=rand()%10;//種群 
373     t2=rand()%10;//變換位
374     temp=group[t1].city[t2];
375     group[t1].city[t2]=group[t1].city[9-t2];
376     group[t1].city[9-t2]=temp;
377     group[t1].city[10]=group[t1].city[0];   
378    }
379    jisuan();
380    savebest();
381    changebestgroup();
382 }
383 /*主函數*/
384 int  main()//最優解20.485281
385 {
386   int j,flag,tuichu=1;
387   double distancenum;
388   while(tuichu)
389   {
390     distancenum=0.0;
391     flag=1;
392     chushigroup();
393     while(flag)
394     {
395     printf("請輸入種群繁衍代數(1000以內):");
396     scanf("%d",&die);
397     if(die<=1000)
398     {
399     flag=0;
400     }
401   }
402   while(die--)
403   {
404      xuanze();
405      jiaocha();
406      bianyi();
407   }
408  printf("最優種群是:\n");
409       for(j=0;j<citynum+1;j++)
410       {
411         printf("%d ",groupbest[j]);
412         if(j<citynum)
413         {
414         distancenum+=distance[groupbest[j]][groupbest[j+1]];
415         }
416       }
417       printf("距離為:%f,適應度為:%f,代數:%d\n\n",distancenum,groupbestfit,dai);
418       printf("繼續產生新種群請按輸入1,退出請輸入0:");
419       scanf("%d",&tuichu); 
420       printf("\n");
421     }
422       return 0;
423 }

 


免責聲明!

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



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