遺傳演算法的簡單應用-巡迴旅行商(TSP)問題的求解
上篇我們用遺傳演算法求解了方程,其中用到的編碼方式是二進位制的編碼,實現起來相對簡單很多,
就連交配和變異等操作也是比較簡單,但是對於TSP問題,就稍微複雜一點,需要有一定的策略,
才能較好的實現。
這次的TSP問題的題目是:
隨機產生10~30個城市,每個城市之間的距離也是隨機產生,距離的範圍是[1,50],求最優的路徑
==========================================================
下面就是具體的求解,由於我的策略是基於知網上的《一種改進的遺傳演算法求解TSP問題》這篇文章,
所以把這篇文章先放上來。下面我簡單介紹一下這篇文章關於求解TSP問題的策略,然後附上參照這篇
文章寫的程式碼。
策略簡述:
1、染色體編碼
為了充分利用城市間相鄰邊的資訊和距離的資訊,不採用二進位制編碼而是採用整數編碼,即每個染
色體都是一個1到N的排列,表示周遊路線的城市間的先後順序序列。
/// <summary> /// 初始化群體 /// </summary> static void Initial_Group() { int count = 0;//已產生個體數 List<int> myRoad = new List<intView Code>(); string road = string.Empty;//個體的表現型 for (int i = 0; i < city_count; i++) { myRoad.Add(i); } while (count < gruopCount)//20個個體 { int tmp, index; for (int i = 0; i < myRoad.Count; i++)//隨機交換得到一組路徑(個體) { index = rd.Next(myRoad.Count); tmp = myRoad[i]; myRoad[i] = myRoad[index]; myRoad[index] = tmp; } for (int i = 0; i < myRoad.Count; i++)//將個體轉化成路徑存到種群中 { road += MyEncode(myRoad[i]);//自己編碼如果大於10,轉化成對應的符號 } group.Add(road); count++; road = ""; } //foreach (var item in group) //{ // Console.WriteLine(item + " " + Ca(item)); //} }
2、選擇運算元
這裡沒有采用輪盤賭演算法,而是採用了錦標賽選擇,即每次從群體中隨機抽 取出M個個體,然後選
擇其中適應值最好的進入下 一代,接著進行下一個錦標賽選擇直至選出種群數目個個體為止。錦標
賽選擇運算元保證了更優的染色體能夠有更大的存活機會,當然,同一個優秀的個體被多次選中也是
允許的。這裡的M取2.
/// <summary> /// 選擇(錦標賽) /// 思路:1.產生兩個不同的隨機數作為要參加錦標賽的兩個個體;2.判斷適應度大小, /// 小的被選擇(最短路徑) /// </summary> /// <param name="list">適應度集合</param> static void Selection(List<int> list) { int count = 0;//選擇的數量 List<string> result = new List<string>();//選擇的結果 while (count < gruopCount) { int test1 = rd.Next(0, gruopCount);//要參加錦標賽的的兩個個體 int test2 = rd.Next(0, gruopCount); if (test1 != test2) { if (list[test1] >= list[test2]) { result.Add(group[test2]); } else { result.Add(group[test1]); } } count++; } //更新種群資訊 for (int i = 0; i < result.Count; i++) { group[i] = result[i]; } }View Code
3、交配運算元
利用相鄰邊的資訊和距離的資訊作為啟發來設計一種基於頂點的候選表的交配運算元(CandidateCrossover)
具體步驟如下:
①交配運算元根據交配概率pc來選擇兩個父體進行交配。把這兩個父體看作是兩個迴圈佇列,然後產生一個
隨機數k作為交配位。 交配位所對應的父體1中的城市作為當前的城市加入到子代1中;
②找到該城市在父體2中對應的位置,分別把該城市在父體1和 父體2中的左右鄰接城市(和這個城市相連線
的邊的另外兩個城市)加入到當前城市的候選表中,如果其左右鄰接城市已在子代中,則選擇其次鄰接城市,
依次類推直至所選取的城市不在子 代中,而最後一個城市的下一個城市是第一個城市;
③根據距離矩陣計算當前城市和候選表中的各個城市的距離,找出與當前城市距離最短的城市加入到子代中,
並且把這個距離最短的城市又當作當 前城市,找到此城市在父體1中的位置,返回到第2 步繼續執行,
直到所有的城市加入到子代1中為止。
/// <summary> /// 基於鄰接表的交叉實現 /// </summary> /// <param name="str1">個體1的染色體</param> /// <param name="str2">個體2的染色體</param> /// <returns></returns> static string Candidate_Crossover(string str1, string str2) { int k = rd.Next(city_count); int j = 0; List<int> list = new List<int>();//子代 List<int> tmp = new List<int>();//候選表 string result = string.Empty; string son = str1[k].ToString();//第一個個體選的城市 int current = MyDecode(son);//當前城市 list.Add(MyDecode(son)); while (list.Count < city_count) { if (list.Count < city_count - 1) { for (int i = 0; i < str2.Length; i++) { if (MyDecode(str2[i].ToString()) == current) { j = i; break; } } string str = ""; //str1 左邊 for (int i = 0; i < str1.Length; i++) { str = str1[(k - i + city_count) % city_count].ToString(); if (!list.Contains(MyDecode(str))) { tmp.Add(MyDecode(str)); break; } } //str1 右邊 for (int i = 0; i < str1.Length; i++) { str = str1[(k + i) % city_count].ToString(); if (!list.Contains(MyDecode(str))) { tmp.Add(MyDecode(str)); break; } } //str2 左邊 for (int i = 0; i < str2.Length; i++) { str = str2[(j - i + city_count) % city_count].ToString(); if (!list.Contains(MyDecode(str))) { tmp.Add(MyDecode(str)); break; } } //str2 右邊 for (int i = 0; i < str2.Length; i++) { str = str2[(j + i) % city_count].ToString(); if (!list.Contains(MyDecode(str))) { tmp.Add(MyDecode(str)); break; } } Dictionary<int, int> dic = new Dictionary<int, int>(); foreach (var item in tmp) { if (!dic.ContainsKey(item)) { dic.Add(item, distance[current, item]); } } int minKey = dic.OrderBy(s => s.Value).Select(s => s.Key).FirstOrDefault(); for (int i = 0; i < str1.Length; i++) { if (minKey == MyDecode(str1[i].ToString())) { k = i; current = minKey; list.Add(minKey); break; } } tmp.Clear(); } else if (list.Count == city_count - 1) { foreach (var a in str1) { if (!list.Contains(MyDecode(a.ToString()))) { list.Add(MyDecode(a.ToString())); } } } } foreach (var item in list) { result += MyEncode(item); } return result; }View Code
4、變異運算元
變異操作發生在某個染色體的某個基因上,它將可變性引入群體中,增強了群體的多樣性,從而提供了從區域性
最優中跳出來的一種手段。變異方法也是一個隨機的、盲目的變異,因此需要使用比較小的變異概率(pm)
來控制以避免造成種群的破壞。這裡採用的是基於位置的變異也稱為插入式變異(InsertionMutation),該運算元
在染色體中隨機產生兩個變異位,然後把第二個位置的基因插入到第一個位置的基因之前。
/// <summary> /// 插入式變異(ISM) /// 思路:1.產生兩個隨機位置;2.將第二個位置的數放在第一個位置的數的前面 /// </summary> /// <param name="str1">要變異的個體</param> /// <returns>變異過後的個體</returns> static string Opreation_Mutation(string str1) { int pos1 = rd.Next(1, city_count);//產生兩個位置 int pos2 = rd.Next(1, city_count); if (pos1 < pos2) { string s = str1[pos2 - 1].ToString(); return str1.Insert(pos1 - 1, s).Remove(pos2, 1); } else if (pos1 > pos2) { string s = str1[pos1 - 1].ToString(); return str1.Insert(pos2 - 1, s).Remove(pos1, 1); } else//如果相等的兩個位置,則重新產生小範圍的隨機數 { int pos3 = rd.Next(1, city_count / 2); int pos4 = rd.Next(city_count / 2, city_count); string s = str1[pos2 - 1].ToString(); return str1.Insert(pos1 - 1, s).Remove(pos2, 1); } }View Code
其中的方法,實現不唯一,時間比較敢,也沒有作一定的優化,只是為了熟悉演算法而寫的。
希望能幫到初學者,也歡迎各位提出修改意見。
完整程式碼可在這下載