一、步驟:
二、重點:
1、編碼
由於遺傳算法不能直接處理問題空間的數據,所以我們必須將問題空間的數據映射成遺傳空間的基因型串結構數據,而算法程序是可以處理遺傳空間的基因型 串結構數據的。比如現在要計算北京、天津、廣東、新疆這四個城市的一條最優路徑,但算法程序不能夠直接處理北京、天津、廣東、新疆這些數據,所以我們得給 它們編上號,北京(0)、天津(1)、廣東(2)、新疆(3),路徑(天津->新疆->北京->廣東)可以表示成基因型串結構數據 (1302),這樣算法程序只要直接處理它們的編號就行了。
(1)二進制編碼,基因用0或1表示(常用於解決01揹包問題)
如:基因A:00100011010 (代表一個個體的染色體)
(2)互換編碼(用於解決排序問題,如旅行商問題和調度問題)
如旅行商問題中,一串基因編碼用來表示遍歷的城市順序,如:234517986,表示九個城市中,先經過城市2,再經過城市3,依此類推。
(3)樹形編碼(用於遺傳規劃中的演化編程或者表示)
如問題:給定了很多組輸入和輸出。請你爲這些輸入輸出選擇一個函數,使得這個函數把每個輸入儘可能近地映射爲輸出。
編碼方法:基因就是樹形結構中的一些函數。
(4)值編碼 (二進制編碼不好用時,解決複雜的數值問題)(源代碼中使用類似此方法)
在值編碼中,每個基因就是一串取值。這些取值可以是與問題有關任何值:整數,實數,字符或者其他一些更復雜的東西。
2、適應度函數
遺傳算法對一個個體(解)的好壞用適應度函數值來評價,適應度函數值越大,解的質量越好。適應度函數是遺傳算法進化過程的驅動力,也是進行自然選擇的唯一標準,它的設計應結合求解問題本身的要求而定。
如TSP問題,遍歷各城市路徑之和越小越好,這樣可以用可能的最大路徑長度減去實際經過的路徑長度,作爲該問題的適應度函數。
3、選擇
在進化中,適者生存,所以在遺傳算法運行過程中,會不斷地選擇優者保留下來,同時也不斷地選擇劣者淘汰下去。選擇算子有很多種方法,以下是比較簡單常見的方法:
(1)排序選擇方法(源代碼中使用類似此方法)
排序選擇方法是指在計算每一個個體的適應度(源代碼中將路徑總長度當適應度)之後,根據適應度大小對個體排序,然後按照事先設置好的概率表按序分配給個體,作爲個自的選擇概率。
(2)適應度比例方法
適應度比例方法是目前遺傳算法中最基本也是最常用的選擇方法,即各個個體的選擇概率和其適應度值成正比。
(3)最佳個體保留方法
即把羣體中適應度最高的個體不進行交叉而直接複製到下一代。
4、交叉
生物進化的核心作用是生物遺傳基因的重組,相對應,遺傳算法的核心操作是交叉算子,所謂交叉是指兩個或兩個以上的父代個體的部分結構加以替換重組而生成新個體的操作。
(1)單交叉點法 (用於二進制編碼)
選擇一個交叉點,子代在交叉點前面的基因從一個父代基因那裏得到,後面的部分從另外一個父代基因那裏得到。
如:交叉前:
00000|01110000000010000
11100|00000111111000101
交叉後:
00000|00000111111000101
11100|01110000000010000
(2)雙交叉點法 (用於二進制編碼)
選擇兩個交叉點,子代基因在兩個交叉點間部分來自一個父代基因,其餘部分來自於另外一個父代基因.
如:交叉前:
01 |0010| 11
11 |0111| 01
交叉後:
11 |0010| 01
01 |0111| 11
(3)基於“ 與/或 ”交叉法 (用於二進制編碼)
對父代按位"與”邏輯運算產生一子代A;按位”或”邏輯運算產生另一子代B。該交叉策略在解揹包問題中效果較好 .
如:交叉前:
01001011
11011101
交叉後:
01001001
11011111
(4)單交叉點法 (用於互換編碼)
選擇一個交叉點,子代的從初始位置出發的部分從一個基因複製,然後在另一個基因中掃描,如果某個位點在子代中沒有,就把它添加進去。
如:交叉前:
87213 | 09546
98356 | 71420
交叉後:
87213 | 95640
98356 | 72104
(5)部分匹配交叉(PMX)法(用於互換編碼)
先隨機產生兩個交叉點,定義這兩點間的區域爲匹配區域,並用交換兩個父代的匹配區域。
父代A:872 | 130 | 9546
父代B:983 | 567 | 1420 變爲:
TEMP A: 872 | 567 | 9546
TEMP B: 983 | 130 | 1420
對於 TEMP A、TEMP B中匹配區域以外出現的數碼重複,要依據匹配區域內的位置逐一進行替換。匹配關係:1<——>5 3<——>6 7<——>0
子代A:802 | 567 | 9143
子代B:986 | 130 | 5427
(6)順序交叉法(OX) (用於互換編碼)
從父代A隨機選一個編碼子串,放到子代A的對應位置;子代A空餘的位置從父代B中按B的順序選取(與己有編碼不重複)。同理可得子代B。
父代A: 872 | 139 | 0546
父代B: 983 | 567 | 1420
交叉後:
子代A: 856 | 139 | 7420
子代B: 821 | 567 | 3904
(7)循環交叉(CX)(用於互換編碼)
CX同OX交叉都是從一個親代中取一些城市,而其它城市來自另外一個親代,但是二者不同之處在於:OX中來自第一個親代的編碼子串是隨機產生的,而CX卻不是,它是根據兩個雙親相應位置的編碼而確定的。
父代A:1 2 3 4 5 6 7 8 9
| | | | |
父代B:5 4 6 9 2 3 7 8 1
可得循環基因:1->5->2->4->9->1
用循環的基因構成子代A,順序與父代A一樣
1 2 4 5 9
用父代B剩餘的基因填滿子代A:
1 2 6 4 5 3 7 8 9
子代B的編碼同理。(循環基因 5->1->9->4->2->5)
(8)兩交換啓發交叉(HGA)
兩交換啓發交叉方法的基本思想如下:
以8個城市的旅行商問題爲例:設N個城市爲1,2,3,4,5,6,7,8
隨機選出一對未匹配的雙親如下:
A = 3 7 2 6 4 8 1 5
B = 4 8 3 2 1 7 5 6
隨機選出初始城j=1(位置號),Sj=1(城市號),右轉動使1成爲兩雙親的第一位置城市如下:
A1 = 1 5 3 7 2 6 4 8
B1 = 1 7 5 6 4 8 3 2
O1 = 1 × × × × × × ×
判斷因爲d(1,5)>d(1,7)則A1以7爲中心右轉得如下中間代:
A2 = × 7 2 6 4 8 5 3
B2 = × 7 5 6 4 8 3 2
O2 = 1 7 × × × × × ×
從第2位開始繼續重複上述過程
判斷因爲d(7,5)=d(7,2)則可任選5或2,A2以5爲中心右轉得如下中間代:
A3 = × 7 5 3 2 6 4 8
B3 = × 7 5 6 4 8 3 2
O3 = 1 7 5 × × × × ×
如此反覆得出
O = 1 7 5 3 2 6 4 8
(9)三交換啓發交叉(THGA)(源代碼中使用類似此方法)
三交換啓發交叉方法的基本思想如下:
選3個參加交配的染色體作爲父代,以8個城市爲例來說明這一過程,其中dij由前面的表1給出,父代染色體爲
A = 3 2 1 4 8 7 6 5
B = 2 4 6 8 1 3 5 7
C = 8 7 5 6 4 3 2 1
SUM1=42,SUM2=40,SUM3=46(SUM1,SUM2,SUM3分別爲這3種排法所走的距離總和數).
隨機選出初始城市j=1,Sj=3右轉動,使3成爲3父代的第1位置.
A = 3 2 1 4 8 7 6 5
B = 3 5 7 2 4 6 8 1
C = 3 2 1 8 7 5 6 4
由於d(3,2)>d(3,5),所以有:
A = × 5 2 1 4 8 7 6
B = × 5 7 2 4 6 8 1
C = × 5 6 4 2 1 8 7
由此規則計算可得:
O = 3 5 7 6 8 4 2 1
5、變異
變異是指依據變異概率將個體編碼串中的某些基因值用其它基因值來替換,從而形成一個新的個體。GA中的變異運算是產生新個體的輔助方法,它決定了GA的局部搜索能力,同時保持種羣的多樣性。交叉運算和變異運算的相互配合,共同完成對搜索空間的全局搜索和局部搜索。
注:變異概率Pm不能太小,這樣降低全局搜索能力;也不能太大,Pm > 0.5,這時GA退化爲隨機搜索。
(1)基本位變異算子(用於二進制編碼)
基本位變異算子是指對個體編碼串隨機指定的某一位或某幾位基因作變異運算。對於基本遺傳算法中用二進制編碼符號串所表示的個體,若需要進行變異操作的某一基因座上的原有基因值爲0,則變異操作將其變爲1;反之,若原有基因值爲1,則變異操作將其變爲0。
變異前:
000001110000000010000
變異後:
000001110001000010000
(2)逆轉變異算子(用於互換編碼)(源代碼中使用類似此方法)
在個體中隨機挑選兩個逆轉點,再將兩個逆轉點間的基因交換。
變異前:
1346798205
變異後:
1246798305
6、運行參數
GA運行時選擇的參數應該視解決的具體問題而定,到目前爲止,還沒有一個適用於GA所有應用領域的關於算法參數的理論。下面是一般情況下使用GA時推薦的參數:
(1)交叉率
交叉率一般來說應該比較大,推薦使用80%-95%。
(2)變異率
變異率一般來說應該比較小,一般使用0.5%-1%最好。
(3)種羣的規模
種羣規模指的是羣體中個體的個數。實驗發現,比較大的種羣的規模並不能優化遺傳算法的結果。種羣的大小推薦使用20-30,一些研究表明,種羣規模 的大小取決於編碼的方法,具體的說就是編碼串(Encoded String)的大小。也就是說,如果說採用32位爲基因編碼的時候種羣的規模大小最好爲32的話,那麼當採用16位爲基因編碼時種羣的規模相應應變爲原 來的兩倍。
(4)遺傳運算的終止進化代數
個人的想法是,設定一個計數器,如果連續N代出現的最優個體的適應度都一樣時,(嚴格的說應該是,連續N代子代種羣的最優個體適應度都<=父代最優個性的適應度)可以終止運算。也可以簡單的根據經驗固定進化的代數。
三、源代碼如下,已加詳細註釋
- /*
- * 編寫人:遠航之帆
- * 時間:2011.8.26
- * 問題:旅行商問題
- * 算法:遺傳算法
- * 作用:通過遺傳算法計算一條最優路徑並輸出,該路經經歷過以下地圖的10個城市,並返回起始點,其中的這條路徑長度最短或接近最短
- */
- #include "stdio.h"
- #include "string.h"
- #include "stdlib.h"
- #define city_num 10 //城市個數
- #define unit_num 50 //羣體規模爲50
- #define pc 0.9 //交叉概率爲0.9
- #define pv 0.1 //變異概率爲10%
- #define ps 0.6 //進行選擇時保留的比例
- #define genmax 15 //最大代數15
- /* 定義個體信息 */
- typedef struct unit
- {
- int path[city_num]; //個體的路徑信息
- int length; //個體代價值
- }Unit;
- typedef struct node
- {
- int city;
- struct node *next;
- }Node;
- Unit group[unit_num]; //種羣變量group
- Unit bestone; //記錄最短路徑
- int generation_num=0; //記錄當前達到第幾代
- /***************************************************************************/
- /* 城市地圖的距離信息: */
- /* 北京 天津 武漢 深圳 長沙 成都 杭州 西安 拉薩 南昌 */
- /* (0) (1) (2) (3) (4) (5) (6) (7) (8) (9) */
- /* 北京(0) 0 1 1272 2567 1653 2097 1425 1177 3947 1 */
- /* 天津(1) 1 0 1 2511 1633 2077 1369 1157 3961 1518 */
- /* 武漢(2) 1272 1 0 1 380 1490 821 856 3660 385 */
- /* 深圳(3) 2567 2511 1 0 1 2335 1562 2165 3995 933 */
- /* 長沙(4) 1653 1633 380 1 0 1 1041 1135 3870 456 */
- /* 成都(5) 2097 2077 1490 2335 1 0 1 920 2170 1920 */
- /* 杭州(6) 1425 1369 821 1562 1041 1 0 1 4290 626 */
- /* 西安(7) 1177 1157 856 2165 1135 920 1 0 1 1290 */
- /* 拉薩(8) 3947 3961 3660 3995 3870 2170 4290 1 0 1 */
- /* 南昌(9) 1 1518 385 993 456 1920 626 1290 1 0 */
- /***************************************************************************/
- int length_table[10][10]={{0,1,1272,2567,1653,2097,1425,1177,3947,1},//城市地圖映射表
- {1,0,1,2511,1633,2077,1369,1157,3961,1518},
- {1272,1,0,1,380,1490,821,856,3660,385},
- {2567,2511,1,0,1,2335,1562,2165,3995,933},
- {1653,1633,380,1,0,1,1041,1135,3870,456},
- {2097,2077,1490,2335,1,0,1,920,2170,1920},
- {1425,1369,821,1562,1041,1,0,1,4290,626},
- {1177,1157,856,2165,1135,920,1,0,1,1290},
- {3947,3961,3660,3995,3870,2170,4290,1,0,1},
- {1,1518,385,993,456,1920,626,1290,1,0}};
- /*****************************************************************************/
- /* */
- /* others */
- /* */
- /*****************************************************************************/
- /* 檢查k是否在son[city_num]中已出現過 */
- Node *search_son(Node *p,int k)
- {
- while(p->next->city!=k)
- p=p->next;
- return p;
- }
- /* 將種羣中個體按代價從小到大排序 */
- void Sort(Unit group[unit_num])
- {
- int i,j;
- Unit temp,*p1,*p2;
- for(j=1;j<=unit_num-1;j++) //排序總共需進行N-1輪
- {
- for(i=1;i<=unit_num-1;i++)
- {
- p1=&group[i-1];
- p2=&group[i];
- if(p1->length>p2->length) //總長度大的往後排
- {
- memcpy(&temp,p1,sizeof(Unit));
- memcpy(p1,p2,sizeof(Unit));
- memcpy(p2,&temp,sizeof(Unit));
- }//end if
- }//end 一輪排序
- }//end 排序
- if(group[0].length < bestone.length) //記錄最優個體
- memcpy(&bestone,&group[0],sizeof(Unit));
- }
- /* 計算某個路徑的總長度 */
- void Calculate_length(Unit *p)
- {
- int j;
- p->length=0;
- for(j=1;j<=city_num-1;j++)
- {
- p->length+=length_table[p->path[j-1]][p->path[j]];
- }
- p->length+=length_table[p->path[city_num-1]][p->path[0]];
- }
- /* 生成一個介於兩整型數之間的隨機整數 */
- int RandomInteger(int low,int high)
- {
- int rand_num;
- double d;
- d=(double)(rand()%100)/100.0; //d>0&&d<1
- rand_num=(int)(d*(high-low+1))+low;
- return rand_num;
- }
- /* 輸出當代種羣中的每個個體 */
- void Print_optimum(Unit group[unit_num],int k)
- {
- int i,j;
- Unit *p;
- printf("當前第 %d 代:\n",k);
- for(i=0;i<=unit_num-1;i++)
- {
- printf("第%2d代,個體%2d :",k,i);
- p=&group[i];
- for(j=0;j<=city_num-1;j++)
- printf("%d ",p->path[j]);
- printf(" 總路徑長度爲:%d \n",p->length);
- }
- }
- /* 輸出最優個體 */
- void Print_bestone(Unit *bestone)
- {
- int i;
- Unit *p=bestone;
- printf("\n\n最優路徑爲:\n");
- for(i=0;i<=city_num-1;i++)
- printf("%d-->",p->path[i]);
- printf("%d 總路徑長度爲:%d \n",p->path[0],p->length);
- }
- /*****************************************************************************/
- /* */
- /* module */
- /* */
- /*****************************************************************************/
- /* 交叉 */
- void Cross_group(Unit *p1,Unit *p2,Unit *p3)
- {
- int i,j,k,Cross_group_point;
- int a,b,c;
- int son[city_num];
- Node *current1,*current2,*current3;
- Node *top1,*top2,*top3;
- Node *p;
- //將三個父代路徑改爲單循環鏈表
- top1=current1=(Node *)malloc(sizeof(Node)); //爲第一個父代路徑生成頭一個結點
- top1->city=p1->path[0];
- top2=current2=(Node *)malloc(sizeof(Node)); //爲第二個父代路徑生成頭一個結點
- top2->city=p2->path[0];
- top3=current3=(Node *)malloc(sizeof(Node)); //爲第三個父代路徑生成頭一個結點
- top3->city=p3->path[0];
- for(i=1;i<city_num;i++) //生成三個父代路徑的其他結點
- {
- current1->next=(Node *)malloc(sizeof(Node));
- current1=current1->next;
- current1->city=p1->path[i];
- current2->next=(Node *)malloc(sizeof(Node));
- current2=current2->next;
- current2->city=p2->path[i];
- current3->next=(Node *)malloc(sizeof(Node));
- current3=current3->next;
- current3->city=p3->path[i];
- }
- current1->next=top1; //鏈接第一個父代路徑頭尾結點
- current2->next=top2; //鏈接第二個父代路徑頭尾結點
- current3->next=top3; //鏈接第三個父代路徑頭尾結點
- Cross_group_point=RandomInteger(0,city_num-1); //三交換啓發交叉
- while(Cross_group_point--)
- current1=current1->next;
- current2=search_son(top2,current1->next->city);
- current3=search_son(top3,current1->next->city);
- for(i=0;i<city_num;i++)
- {
- son[i]=current1->next->city; //將最優點存入son
- p=current1->next; //從第一個父代路徑鏈表中刪除該最優點
- current1->next=p->next;
- free(p);
- p=current2->next; //從第二個父代路徑鏈表中刪除該最優點
- current2->next=p->next;
- free(p);
- p=current3->next; //從第三個父代路徑鏈表中刪除該最優點
- current3->next=p->next;
- free(p);
- if(i==9)break;
- a=length_table[son[i]][current1->next->city];
- b=length_table[son[i]][current2->next->city];
- c=length_table[son[i]][current3->next->city];
- if(a<=b&&a<=c){ //比較最短踞離後,將三條父代路徑鏈表定位於最優點的前一個節點
- current2=search_son(current2,current1->next->city);
- current3=search_son(current3,current1->next->city);
- }
- else if(b<=a&&b<=c){
- current1=search_son(current1,current2->next->city);
- current3=search_son(current3,current2->next->city);
- }
- else if(c<=a&&c<=b){
- current1=search_son(current1,current3->next->city);
- current2=search_son(current2,current3->next->city);
- }
- }
- memcpy(p1->path,son,sizeof(p1->path)); //生成第一個子代路徑
- Cross_group_point=RandomInteger(0,city_num-1); //生成第二個子代路徑
- for(k=0,i=Cross_group_point;i<city_num;i++)
- p2->path[k++]=son[i];
- for(i=0;i<Cross_group_point;i++)
- p2->path[k++]=son[i];
- Cross_group_point=RandomInteger(0,city_num-1); //生成第三個子代路徑
- for(k=0,i=Cross_group_point;i<city_num;i++)
- p3->path[k++]=son[i];
- for(i=0;i<Cross_group_point;i++)
- p3->path[k++]=son[i];
- Calculate_length(p1); //計算子代p1路徑的總長度
- Calculate_length(p2); //計算子代p2路徑的總長度
- Calculate_length(p3); //計算子代p3路徑的總長度
- }
- /* 變異 */
- void Varation_group(Unit *group,int gen_num)
- {
- int flag,i,j,k,temp;
- Unit *p;
- flag=(gen_num>50)?(3*100*pv):(100*pv); //在進化後期,增大變異概率
- while(flag--)
- {
- i=RandomInteger(0,unit_num-1); //確定發生變異的個體
- j=RandomInteger(0,city_num-1); //確定發生變異的位
- k=RandomInteger(0,city_num-1);
- p=&group[i]; //變異
- temp=p->path[j];
- p->path[j]=p->path[k];
- p->path[k]=temp;
- Calculate_length(p); //重新計算變異後路徑的總長度
- }
- }
- /* 初始化最優值 */
- void Initial_bestone(Unit *bestone)
- {
- bestone->length=RAND_MAX;
- }
- /* 初始化種羣 */
- void Initial_group(Unit *group)
- {
- int i,j,k;
- Unit *p;
- for(i=0;i<=unit_num-1;i++) //初始化種羣裏的100個個體
- {
- p=group+i; //p指向種羣的第i個個體
- for(j=0;j<=city_num-1;j++) //初始化每個個體的路徑
- {
- k=0;
- if(j==0) p->path[j]=RandomInteger(0,city_num-1);
- else
- {
- p->path[j]=RandomInteger(0,city_num-1);
- while(k<j)
- { //與之前城市重複,重新生成一個城市
- if(p->path[j]==p->path[k])
- {
- p->path[j]=RandomInteger(0,city_num-1);
- k=0;
- }
- else k++;
- }//end while
- }
- }//end for //初始化路徑
- Calculate_length(p); //計算該路徑的總長度
- }//end for //初始化種羣
- }
- /* 進化種羣 */
- void Evolution_group(Unit *group)
- {
- int i,j;
- int temp1,temp2;
- temp1=unit_num*(1-ps);
- temp2=unit_num*ps;
- for(i=1;i<=genmax;i++)
- {
- //選擇
- Sort(group);
- Print_optimum(group,i-1); //輸出當代(第i-1代)種羣
- for(j=0;j<=temp1-1;j++)
- memcpy(&group[j+temp2],&group[j],sizeof(Unit));
- //交叉
- for(j=0;j<unit_num-2;)
- {
- Cross_group(&group[j],&group[j+1],&group[j+2]);
- j+=3;
- }
- //變異
- Varation_group(group,i);
- }
- Sort(group);
- Print_optimum(group,i-1); //輸出當代(第i-1代)種羣
- }
- /*主函數*/
- int main()
- {
- srand((int)time(NULL)); //初始化隨機數發生器
- Initial_bestone(&bestone); //初始化最優值
- Initial_group(group); //初始化種羣
- Evolution_group(group); //種羣進化:選擇、交叉、變異
- Print_bestone(&bestone);
- return 0;
- }