TSP遗传算法解决——人工智能

实验四 TSP问题的遗传算法实现

一、实验目的

本实验课程是计算机、智能、物联网等专业学生的一门专业课程,通过实验,帮助学生更好地掌握人工智能相关概念、技术、原理、应用等;通过实验提高学生编写实验报告、总结实验结果的能力;使学生对智能程序、智能算法等有比较深入的认识。要掌握的知识点如下:
1.掌握人工智能中涉及的相关概念、算法;
2.熟悉人工智能中的知识表示方法;
3.掌握问题表示、求解及编程实现;
4.熟悉和掌握遗传算法的基本概念和基本思想;
5.理解和掌握遗传算法的各个操作算子,能够用选定的编程语言设计简单的遗传优化系统;
6.通过实验培养学生利用遗传算法进行问题求解的基本技能。

二、基本要求

1.实验前,复习《人工智能》课程中的有关内容。
2.准备好实验数据。
3.程序可以组队完成(要标明完成部分,如各函数的完成者),程序应加适当的注释。
4.完成实验报告,由小组完成报告要有明显区别,分析和总结应该按照自己完成的部分进行。

三、实验软件

推荐使用C或C++(Visual studio等平台)(不限制语言使用,如Java,matlab,Python等都可以)。

四、实验内容:

以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 Oliver TSP问题的10个城市位置坐标
城市编号 坐标
1 (87,7)
2 (91,38)
3 (83,46)
4 (71,44)
5 (64,60)
6 (68,58)
7 (83,69)
8 (87,76)
9 (74,78)
10 (71,71)
有人求得的最优路径为: 0 3 5 4 9 8 7 6 2 1 0
路径长度是166.541336
上述10个城市的求解中编号从0开始,把所有路径搜索完又返回到出发节点。
2 问题描述
应用遗传算法求解30/10个节点的TSP(旅行商问题)问题,求问题的最优解。

五、实验程序组成:

※全局常量:
const int city_num = 10;//城市的数量,即基因的个数
const int Population_num = 100;//群体规模
double pc = 0.8;//交叉概率
double pm = 0.3;//变异概率
double dis[city_num][city_num];//城市间的距离矩阵
※自定义类型:
struct City { //城市
int label;//城市编号
int x;//该城市在坐标轴x上的位置
int y;//该城市在坐标轴y上的位置
};
※函数:
//计算两个城市之间的距离
double distance_two_city(const City& city1, const City& city2)
//返回当前路径的总长度
double distance_all_city(vector& vec)
//初始化
void init()
//显示当前的最短路径
void show_path(vector& city)
//选择(采用联赛选择及精英保存策略)
void BettingWheelSelection()
//单点交叉并消除冲突
void Mating()
//变异
void Variation()

六、实验结果分析

(1)求出问题最优解,若得不出最优解,请分析原因;
TSP遗传算法解决——人工智能_第1张图片

得出最优解166.541,路径为9→8→7→6→2→1→0→3→5→4→9

(2)要求界面显示每次迭代求出的局部最优解和最终求出的全局最优解。
TSP遗传算法解决——人工智能_第2张图片

每代结果都有显示

(3)测试种群规模,交叉概率、变异概率对算法结果的影响。(运算速度,内存空间,准确率等)
TSP遗传算法解决——人工智能_第3张图片

※交叉概率:
TSP遗传算法解决——人工智能_第4张图片

※变异概率:
TSP遗传算法解决——人工智能_第5张图片

※种群规模:
TSP遗传算法解决——人工智能_第6张图片

根据上表结果,可知:
①种群规模越大,变异概率越高,交叉概率越高,种群的平均水平将更高,且更容易得到最优解。
②一般情况下,交叉概率以及变异的概率越大,得到最优解时的迭代次数会相对较少。

七、实验心得:

通过本次实验,对遗传算法的理解更加深刻了。该算法实现的难点交叉算法的设计,由于TSP问题和一般的NP问题不一样,每个个体的每个维度具有唯一性,因此在交叉的时候要注意不能有重复的值。本次实验采用的是部分匹配交叉,将每个路径最后四位作交叉,并且判断该路前前面是否存在相同项目,若存在则与刚交叉过的另一个路径继续交换重复项。另外,这只是10个城市的最优解,当城市数量较多时,大于50个城市,迭代多次,GA仍然不收敛,可能的问题是陷入了局部最优解,因此对GA算法进行改进怡跳出局部最优解,可以采用类似于PSO或者蚁群算法的思想。

实验源码:

#include<iostream>
#include<vector>
#include<fstream>
#include<random>
#include<map>
#include<time.h>
using namespace std;

const int city_num = 10;//城市的数量,即基因的个数
const int Population_num = 100;//群体规模
double pc = 0.8;//交叉概率
double pm = 0.3;//变异概率
double dis[city_num][city_num];//城市间的距离矩阵
//城市的编号及位置
struct City {
	int label;//城市编号
	int x;//该城市在坐标轴x上的位置
	int y;//该城市在坐标轴y上的位置
};
vector<vector<City>> Pop;//种群,即所有个体的集合
//计算两个城市之间的距离
double distance_two_city(const City& city1, const City& city2)
{
	return sqrt(pow((city1.x - city2.x), 2) + pow((city1.y - city2.y), 2));//用欧式距离进行计算
}
//返回当前路径的总长度
double distance_all_city(vector<City>& vec)
{
	double sum = 0;
	int size = vec.size();//城市的数量
	for (int i = 1; i < size; ++i)
		sum += dis[vec[i].label][vec[i - 1].label];
	sum += dis[vec[size - 1].label][vec[0].label];
	return sum;
}
//初始化
void init()
{
	ifstream fin;//读文件对象
	fin.open("city.txt");//打开文件
	vector<City> city;//存放城市的数据
	for (int i = 0; i < city_num; ++i)//从文件中读入各城市的编号及位置坐标
	{
		City temp;
		fin >> temp.label;
		fin >> temp.x;
		fin >> temp.y;
		city.push_back(temp);
	}
	fin.close();
	for (int i = 0; i < city_num; ++i)//计算城市之间的距离
		for (int j = 0; j < city_num; ++j)
			dis[i][j] = dis[j][i] = distance_two_city(city[i], city[j]);
	srand((unsigned)time(NULL));
	for (int i = 0; i < Population_num; ++i)//随机产生100个个体
	{
		vector<City> temp = city;
		for (int j = 0; j < 10; ++j)
			swap(temp[j], temp[rand() % city_num]);
		Pop.push_back(temp);//将该个体加入到种群中
	}
}
//显示当前的最短路径
void show_path(vector<City>& city)
{
	cout << "path:";
	for (int i = 0; i < city_num; ++i)
		cout << city[i].label << ' ';
	cout << city[0].label << endl;
	cout << endl;
}
//选择(采用联赛选择及精英保存策略)
void BettingWheelSelection()
{
	vector<vector<City>> temp;
	map<double, int> bigmap;//用于排序,找出最短距离的路径(默认升序排序)
	for (int i = 0; i < Population_num; ++i)
		bigmap[distance_all_city(Pop[i])] = i;
	int better_fit, worse_fit;
	better_fit = (*bigmap.begin()).second;//当前适应度最好的个体
	worse_fit = (*(--bigmap.end())).second;//当前适应度最差的个体
	double sum;//当前最优路径的距离
	sum = (*bigmap.begin()).first;
	cout << "min-road:" << sum << endl;
	show_path(Pop[better_fit]);//输出该最优路径
	temp.push_back(Pop[better_fit]);//将最好的个体复制两份到新的种群中
	temp.push_back(Pop[better_fit]);
	int M = 3;//精英选择策略中随机选择的个体数
	srand((unsigned)time(NULL));
	for (int i = 0; i < Population_num - 2; ++i)//用联赛选择剩余的个体数
	{
		int fit = INT_MAX;//当前所选个体集合中的最高适应度
		int loc;//适应度最好的个体
		for (int j = 0; j < M; ++j)//从city_num-2个个体中随机选择M个个体
		{
			int pos = rand() % Population_num;
			while (pos == better_fit || pos == worse_fit)//该个体不能是当前种群中适应度最高或最差的个体
				pos = rand() % Population_num;
			if (distance_all_city(Pop[pos]) < fit)//判断当前个体是否适应度最优的个体
			{
				fit = distance_all_city(Pop[pos]);
				loc = pos;
			}
		}
		temp.push_back(Pop[loc]);//用联赛选择策略选择了一个个体,将它加入新的种群中
	}
	Pop = temp;//产生新的种群
}
//单点交叉并消除冲突
void Mating()
{
	vector<City> temp1, temp2;
	int pos = 7;//发生交叉的起始位置(交叉区域:从该位置到末尾)
	srand((unsigned)time(NULL));
	for (int i = 0; i < 48; ++i)
	{
		double rand_rate = rand() / (float)RAND_MAX;
		if (rand_rate < pc)//如果当前的概率小于交叉的概率则进行交叉
		{
			temp1 = Pop[i + 2];//个体1
			temp1[pos] = Pop[99 - i][pos];//交叉
			temp1[pos + 1] = Pop[99 - i][pos + 1];
			temp1[pos + 2] = Pop[99 - i][pos + 2];
			int z = 0;//消除冲突
			for (int j = 0; j < pos; ++j)
			{
				while (Pop[i + 2][z].label == temp1[pos].label || Pop[i + 2][z].label == temp1[pos + 1].label ||
					Pop[i + 2][z].label == temp1[pos + 2].label)
					++z;
				temp1[j] = Pop[i + 2][z];
				++z;
			}
			temp2 = Pop[99 - i];//个体2
			temp2[pos] = Pop[i + 2][pos];//交叉
			temp2[pos + 1] = Pop[i + 2][pos + 1];
			temp2[pos + 2] = Pop[i + 2][pos + 2];
			z = 0;//消除冲突
			for (int j = 0; j < pos; ++j)
			{
				while (Pop[99 - i][z].label == temp2[pos].label || Pop[99 - i][z].label == temp2[pos + 1].label ||
					Pop[99 - i][z].label == temp2[pos + 2].label)
					++z;
				temp2[j] = Pop[99 - i][z];
				++z;
			}
			Pop[i + 2] = temp1;//完成交叉
			Pop[99 - i] = temp2;
		}
	}
}
//变异
void Variation()
{
	srand((unsigned)time(NULL));
	for (int i = 2; i < Population_num; ++i)
	{
		double rand_rate = rand() / (float)RAND_MAX;
		if (rand_rate < pm)//如果当前概率小于变异概率则进行变异
		{
			for (int j = 0; j < 5; ++j)//随机选择5个基因进行变异
			{
				int pos1 = rand() % city_num;//随机选择变异的位置
				int pos2 = rand() % city_num;
				swap(Pop[i][pos1], Pop[i][pos2]);
			}
		}
	}
}

int main()
{
	const int gen_max = 30;//最大迭代数
	init();
	for (int i = 0; i < gen_max; ++i)
	{
		cout << "Generation " << i + 1 << endl;
		BettingWheelSelection();
		Mating();
		Variation();
	}
}

ps:总体代码思路还是学习其他人的,不过后来编译时候总会发生内存区的问题,就不想继续改了,于是代码变得大部分相同

你可能感兴趣的:(人工智能基础)