实验四 TSP问题的遗传算法实现
1. 实验内容
以N个节点的TSP(旅行商问题)问题为例,应用遗传算法进行求解,求出问题的最优解。
1 旅行商问题
旅行商问题(Traveling Salesman Problem, TSP),又译为旅行推销员问题、货担郎问题,简称为TSP问题,是最基本的路线问题。假设有n个可直达的城市,一销售商从其中的某一城市出发,不重复地走完其余n-1个城市并回到原出发点,在所有可能的路径中求出路径长度最短的一条。
TSP问题是组合数学中一个古老而又困难的问题,也是一个典型的组合优化问题,现已归入NP完备问题类。NP问题用穷举法不能在有效时间内求解,所以只能使用启发式搜索。遗传算法是求解此类问题比较实用、有效的方法之一。
下面给出30个城市的位置信息:
表1 Oliver TSP问题的30个城市位置坐标
城市编号 坐标 城市编号 坐标 城市编号 坐标
1 (87,7) 11 (58,69) 21 (4,50)
2 (91,38) 12 (54,62) 22 (13,40)
3 (83,46) 13 (51,67) 23 (18,40)
4 (71,44) 14 (37,84) 24 (24,42)
5 (64,60) 15 (41,94) 25 (25,38)
6 (68,58) 16 (2,99) 26 (41,26)
7 (83,69) 17 (7,64) 27 (45,21)
8 (87,76) 18 (22,60) 28 (44,35)
9 (74,78) 19 (25,62) 29 (58,35)
10 (71,71) 20 (18,54) 30 (62,32)
最优路径为:1 2 3 4 6 5 7 8 9 10 11 12 13 14 15 16 17 19 18 20 21 22 23 24 25 28 26 27 29 30
其路径长度为:424.869292
也可取前10个城市的坐标进行测试:
2. 程序清单
- 定义参数
//定义参数 #define MAX_CITIES 30 //城市数量 #define MAX_INDIVIDUALITY 300 //种群数量 #define Mutated_Rate 30 //变异率 30% #define Generate_Rate 80 //繁衍率 80% #define G_TIMES 500 //迭代次数 int Position[MAX_CITIES][2]; //记录城市坐标 int candidate = 3; //候选个数,用于锦标赛选择算子
- 定义种群个体
class Individualty { public: double Cost; //当前个体的路径长度 vector<int> Path; //路径数组 }Individualties[MAX_INDIVIDUALITY], StartIndividualties[MAX_INDIVIDUALITY]; //在统计时因为需要使用同一个初始种群,所以定义了Individualties数组和StartIndividualties数组 //Individualties数组用于计算过程的迭代。 //StartIndividualties数组用于保存初始种群的信息。
- 随机产生种群:
void RandPath() { for (int i = 0; i < MAX_INDIVIDUALITY; i++) { StartIndividualties[i].Path.resize(MAX_CITIES); bool status[MAX_CITIES] = { false }; //标记数组 for (int j = 0; j < MAX_CITIES; j++) { int index = rand() % MAX_CITIES; //随机产生城市下标 while (status[index]) { //判断是否存在冲突 index = rand() % MAX_CITIES; } status[index] = true; //设置标记 StartIndividualties[i].Path[j] = index; } } }
- 计算种群个体路径代价:
void ComCost(Individualty& a) { a.Cost = 0; for (int j = 0; j < MAX_CITIES; j++) { int dx = Position[a.Path[j]][0] - Position[a.Path[((j + 1) % MAX_CITIES)]][0]; int dy = Position[a.Path[j]][1] - Position[a.Path[((j + 1) % MAX_CITIES)]][1]; double direct = pow(dx, 2) + pow(dy, 2); a.Cost += pow(direct, 0.5); } } //计算种群中所有个体的路径代价,并按小到大排序 void ComPathCost() { for (int i = 0; i < MAX_INDIVIDUALITY; i++) { ComCost(Individualties[i]); } qsort(Individualties, MAX_INDIVIDUALITY, sizeof(Individualty), > (_CoreCrtNonSecureSearchSortCompareFunction)cmp); }
- 输出最优个体(i=0)的路径及代价:
void ShowPath(int i=0) { cout << "cost:" << Individualties[i].Cost << endl; for (int j = 0; j < MAX_CITIES; j++) { cout << Individualties[i].Path[j] << ","; } cout << endl; }
- 选择算子算法:将被选中的个体在种群中的下标放入vector中返回
//最佳选择算子,选择代价最小的前num个个体。 vector<int> BSel(int num) { if (num > MAX_INDIVIDUALITY)return vector<int>(0); vector<int> parents(num); for (int i = 0; i < num; i++) { parents[i] = i; } return parents; } //赌轮盘选择算子,选择num个个体 vector<int> RWSel(int num) { if (num > MAX_INDIVIDUALITY)return vector<int>(0); bool flags[MAX_INDIVIDUALITY] = { false }; vector<int> parents(num); double sum = 0; for (int i = 0; i < MAX_INDIVIDUALITY; i++) {//计算所有个体的总路径代价sum sum += Individualties[i].Cost; } for (int i = 0; i < num; i++) { int r = rand() % ((int)sum); //随机生产一个小于sum的随机数r double temp = 0; for (int j = 0; j < MAX_INDIVIDUALITY; j++) {//判断随机数r落在哪个区间 if (temp + Individualties[j].Cost > r) { if (!flags[j]) { //判断是否已经被选择过 parents[i] = MAX_INDIVIDUALITY - 1 - j;//选择该个体作为父代 } else { j--; } break; } else { temp += Individualties[j].Cost; } } } return parents; } //锦标赛选择算子,candidate=3,选择num个个体 vector<int> TSel(int num) { if (num > MAX_INDIVIDUALITY || candidate > MAX_INDIVIDUALITY)return vector<int>(0); vector<int> parents(num); int* temp = new int[candidate]; for (int i = 0; i < num; i++) { int j = 0; bool status[MAX_INDIVIDUALITY] = { false }; for (; j < candidate;) { //选出candidate个候选 temp[j] = rand() % MAX_INDIVIDUALITY; if (!status[temp[j]]) { //判断是否重复 status[temp[j++]] = true; } } int min_index = 0; for (j = 1; j < candidate; j++) { //从candidate个候选中选出最优的个体 if (Individualties[temp[j]].Cost<Individualties[temp[min_index]].Cost) { min_index = j; } } parents[i] = temp[min_index]; //加入到parents数组,作为父代 } delete[] temp; return parents; }
- 交叉算子:
//交叉算子,对p1,p2为父代,产生一个子代。 vector<int> PMCros(int p1, int p2) { int l = rand() % MAX_CITIES, r = rand() % MAX_CITIES; //随机选择交叉区间[l, r] if (l > r)swap(l, r); vector<int> son(Individualties[p2].Path); for (int i = l; i <= r; i++) { for (int j = 0; j < MAX_CITIES; j++) { if (son[j] == Individualties[p1].Path[i]) { swap(son[j], son[i]); } } } return son; }
交叉算子流程图:
- 变异算子0:
//变异算子0,将第l,r个数据互换 void Mutated0(int i) { if ((rand() % 100) < Mutated_Rate) {//变异概率 int l = rand() % MAX_CITIES; int r = rand() % MAX_CITIES; swap(Individualties[i].Path[l], Individualties[i].Path[r]); } }
变异算子0流程图:
- 变异算子1:
变异算子0在实验中效果并不好,收敛到的结果与最优解差异较大
在结合问题思考后,发现了一种可以收敛结果更优的——变异算子1//变异算子1,将第l-r个数据移动到头部 void Mutated1(int i) { if ((rand() % 100) < Mutated_Rate) {//变异概率 Individualty temp; temp.Cost = 0; temp.Path.resize(MAX_CITIES); int l = rand() % MAX_CITIES, r = rand() % MAX_CITIES; if (l > r)swap(l, r); for (int j = l; j <= r; j++) { temp.Path[j - l] = Individualties[i].Path[j]; } for (int j = 0; j < l; j++) { temp.Path[j + r - l + 1] = Individualties[i].Path[j]; } for (int j = r + 1; j < MAX_CITIES; j++) { temp.Path[j] = Individualties[i].Path[j]; } Individualties[i].Path = temp.Path; ComCost(Individualties[i]); } }
变异算子1流程图:
- 统计结果:
在进行对该案例分别使用变异算子0和变异算子1(除变异算子外,其他参数算子完全相同)进行40次不同初始种群的重复实验,并统计迭代500代后的结果:
40次实验的平均解:
使用变异算子0:最终平均解为:507.407
使用变异算子1:最终平均解为:441.536
可以看出使用变异算子1整体结果比变异算子0要好,得出的解很接近最优解424.869
蓝色线为:变异算子0
橙色线为:变异算子1
绿色线为:变异算子3,与变异算子1差别不大
- 变异算子3
变异算子3,在变异算子1的基础上加入了一点改进,该变异算子加入判优过程,在执行后会判断适应度是否减小。如果适应度减小了,那么r将向后移动一个位置。当r移动到最后一个位置时,执行该变异算子其适应度将不会改变。因为12345和34512适应度是一样的。
//变异算子3,将第l-r个数据移动到头部,并判断是否变优,没有则r++,直到序列末尾 void Mutated3(int i) { if ((rand() % 100) < Mutated_Rate) { Individualty temp; temp.Cost = 0; temp.Path.resize(MAX_CITIES); int l = rand() % MAX_CITIES, r = rand() % MAX_CITIES; if (l > r)swap(l, r); do { for (int j = l; j <= r; j++) { temp.Path[j - l] = Individualties[i].Path[j]; } for (int j = 0; j < l; j++) { temp.Path[j + r - l + 1] = Individualties[i].Path[j]; } for (int j = r + 1; j < MAX_CITIES; j++) { temp.Path[j] = Individualties[i].Path[j]; } ComCost(temp); r++; } while (temp.Cost > Individualties[i].Cost && r != MAX_CITIES);//判断是否变优? Individualties[i].Path = temp.Path; Individualties[i].Cost = temp.Cost; } }
变异算子3流程图:
最优解: