遗传算法的简单应用-巡回旅行商(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<int>(); 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]; } }
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; }
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); } }
其中的方法,实现不唯一,时间比较敢,也没有作一定的优化,只是为了熟悉算法而写的。
希望能帮到初学者,也欢迎各位提出修改意见。
完整代码可在这下载
https://github.com/hwqdt/Catcher.TSP