這次的文章是以一份報告的形式貼上來,代碼只是簡單實現,難免有漏洞,比如循環輸入的控制條件,說是要求輸入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 }