西电人工智能大作业TSP免费超详细保姆注释代码

西电人工智能大作业TSP免费超详细保姆注释代码


大家好,本人代码基础薄弱,人工智能学的也不怎么样,但是期末TSP作业写出来了,网上代码要么没注释看不懂,要么太复杂,要么还要钱,俺深恶痛绝惹~,于是,大家不嫌弃俺的代码的话,可以参考哦,所有解析,注意事项,使用方法,报错处理都在代码注释里,希望大家开开心心哦。下面附带全套代码和保姆级超详细注释

#include<iostream>
#include<string.h>
#include<cstdlib>
#include<ctime>
#include<algorithm>
using namespace std;

/*------------------------代码使用必读说明--------------------------*/
//1. 不同老师给的城市数量可能不一样,可以直接在下列宏定义前,即第21行的CITY_NUMBER变量修改为要求的数量即可
//2. 不同老师给的城市距离下三角矩阵可能不一样,可以在Population::Population(int n)函数第253行处修改文件读取路径即可
//3. 不同老师的输出要求可能不同,相关输出大部分都在各种Show函数里,至于是哪一个show,大家可以看注释说明,也可以自己写
//4. 建议使用Visual Studio打开文件,这样的话注释汉字不容易出现乱码错位
//5. 如果使用Visual Studio运行的时候,可能会提示fscanf或者fprint函数不可用,进行如下设置即可:
//   项目-->属性-->C/C++ -->预处理器-->右侧边栏的“预处理器定义”的值修改为:_CRT_SECURE_NO_WARNINGS
//6. 当你学习代码时候,如果忘了这个变量或者方法的功能,可以鼠标移动到这个变量上,我写的注释说明就会显示出来
//  【前提是Visual Studio】
//7. 由于遗传算法概率影响因素很大,所以最好运行程序20多次左右找到最优解即可
//8. 本人代码基础薄弱,人工智能基础不好,代码很冗长,希望大家不要嫌弃


/*采用整数编码,分别用1,2,3,4,5,6,7,8,9,10, 11, 12, 13, 14, 15, 16, 17代表17个城市。
由这17个数字组成的序列即为不同的路径顺序,也是不同的【基因】*/

//常量CITY_NUMBER【存储城市数量】,这样在这里一处修改便可在修改整个代码中的城市数量
const int CITY_NUMBER = 17;
//用于把老师给的城市距离下三角矩阵文件转换为地图距离的二维数组
int map[CITY_NUMBER][CITY_NUMBER];
//交叉概率变量,当该路径的累计适应度值【累计适应度是什么详见代码前的名字概览】小于0.7就会发生基因片段交叉
#define PC 0.7
//单个基因变异变量,当随机产生的小数小于等于该变量,这个基因【基因是什么详见前文名字概览】位就会产生随机变异
#define PM 0.02
//种群数量变量,存储着初始化的初代种群数量,不可超过Population类中数组的长度
#define NN 200
//迭代变量,存储着需要迭代的轮回数
#define ROUND 400


/*--------------------------------路径类---------------------------------------*/
//【用来存储每一个路径的路线、距离、适应度等信息,同时位外界提供获取、设置、修改这些信息的方法】
class Pop {
public:
	//路径基因 ,int型数组,长度=城市长度+1,因为其存储着路径,路径要包括所有城市,还要回到起点,故而 + 1 ,始化时通过随机数随机生成每一个路径
	int GENE[CITY_NUMBER + 1];
	double DISTANCE;//路径距离,int型变量,该路径的总长度;通过调用相关函数计算生成并存储
	double VALUE; //适应度 ,double型变量,其本质是:DISTANCE / 所有路径的总DISTANCE
	double Q;//累计适应度 Q:double型变量,其本质是:从第一个元素起,到其本身所有元素的适应度之和,如,第N个路径对象的Q就等于:1.VALUE + 2.VLAUE + 3.VALUE + 4.VALUE + ... + N.VALUE
	//Pop类对象初始化函数
	void Pop_Init();
	//Pop类对象计算其路径距离函数
	void Distance();
	//Pop类对象用以输出路径函数
	void Show();
	//Pop类对象用以输出该路径距离的函数
	void Show_Distance();
	//Pop类对象用以输出对象适应度的函数,用于调试观察测试,可删除
	void Show_Value();
	//Pop类对象用以输出对象累计适应度的函数,用于调试观察测试,可删除
	void Show_Q();
};


/*---------------------------------路径组类------------------------------------*/
class Population {
public:
	class Pop P[200];//存储初代种群的Pop类数组,长度暂定为200,可修改
	class Pop P1[200];//存储初代种群中,选择出来的距离最短的子代数组,长度可修改
	class Pop P2[200];//存储P1子代中个体互相交叉产生的交叉子代P2,长度可修改
	int N;//存储城市数量变量,用于控制各个函数中可能的循环次数
	class Pop* begin;//存储初代种群的首元素的指针,用于后期类数组排序函数
	class Pop* end;//存储初代种群的末尾元素的下一位的指针,用于后期类数组排序函数
	int P2_NUMBER;//交叉产生的后代P2的数量
	double DISTANCEP;//初代种群中所有路径距离的总和
	//初始化构造函数,参数列表中的n为输入的城市数量,会转存进上述的变量N中
	Population(int n);
	//把初代种群每一个对象的所有信息输出的函数,用以调试测试观察,可删除
	void Population_Show();
	//把选择出来的所有P1对象的所有信息输出的函数,用以调试测试观察,可删除
	void Population1_Show();
	//把P1对象交叉后产生的所有P2后代的相关信息输出的函数,用以调试观察,可删除
	void Population2_Show();
	//计算初代种群所有对象的路径距离
	void DistanceP();
	//输出初代每一个对象的路径距离,用以调试测试,可删除
	void Show_DistanceP();
	//计算初代P中所有对象的适应度VALUE
	void Got_ValueP();
	//计算初代P中所有对象的累计适应度Q
	void Got_Q();
	//选择函数,参数列表为number【需要选择出来的对象对数量的,两个为一对】
	//另一个参数type,当为0的时候,即为简单的筛选出距离最短的前2*number个对象,并存储到P1中
	//当type为1的时候,则为选择出后代与初代所有个体中前2*number个对象,然后存储的P中作为下一次迭代的新初代
	void Select(int number,int type);
	//对选择出来的P1子代群进行逐对判断,如果该对的前者元素的累计适应度Q<=PC,则这一对元素要进行基因交叉
	void Tran();
	//基因交叉函数,参数列表为要进行交叉的两个对象,index为交叉出来的子代,在P1数组中要存储的位序
	void Crosser(class Pop p1, class Pop p2, int index);
	//用以将交叉产生的P2子代群加入到P初代种群中
	void Add();
	//里面调用了Got-Q,Got-VauleP,DistanceP,Pointer等函数,负责各项数值计算,调用这个大函数即可
	void Canculate();
	//还记得变量指针begin和end吗,这个函数就是用来获取当前初代种群P的始末指针的
	void Pointer();
};
//排序模式函数,具体说明如下
//1. 它不是任何类的成员方法
//2. 我们后期需要对种群类数组进行根据DISTANCE的排序,需要调用C++自带的sort()函数【sort怎么用可以百度学习】
//   而这个函数需要额外定义是降序或者升序排列,这个函数功能就是告诉sort函数我们选择的排序模式
//3. 该函数的排序模式为:升序
bool From(class Pop& p1, class Pop& p2)
{
	return p1.DISTANCE < p2.DISTANCE;
}
//测试函数,显示累计适应度Q
void Pop::Show_Q()
{
	cout << "Q: " << Q << endl;
}
//测试函数,显示适应度值Value
void Pop::Show_Value()
{
	cout << "Value: " << VALUE << endl;
}
//测试函数,显示该路径
void Pop::Show()
{
	for (int count = 0; count < (CITY_NUMBER + 1); count++)
	{
		cout << GENE[count] ;
		if (count != CITY_NUMBER)//这里的if判断是为了让最后一个城市序数输出后,不再继续输出箭头-->,为了美观而已
			cout << "-->";
		else
			cout << endl;
	}
	cout << endl;
}
//路径类对象的初始化函数
void Pop::Pop_Init()
{
	int g;//存储产生的单个基因位,即数组中某一位的城市代码【1~CITY_NUMBER】
	int count;//循环控制变量【后文无特殊说明,count均为循环控制变量】
	for (count = 0; count < CITY_NUMBER; )
	//该循环即为产生基因的部分,一共产生CITY_NUMBER个基因位,最后一个基因位和首位相同,因为要返回起点,故不用额外产生
	{
	label://标签,如果发现产生的基因中有重复出现的基因位【因为题目要求不能重复途径同一城市】,则跳转到这里重新生成基因
		g = (rand() % (CITY_NUMBER)+1);//产生从1到17的随机数,作为基因位
		//cout << g << endl;
		GENE[count] = g;//基因存入当前基因序列的第count位
		for (int index = count - 1; index >= 0; index--)//遍历已经设置好的所有基因位,看看有没有出现重复的
		{
			if (GENE[index] == GENE[count])
			{//发现该基因位和前面第index位基因重复了
				goto label;//跳转,回炉重造
			}
		}
		count++;
	}
	GENE[count] = GENE[0];//将最后一位基因设置位首位基因,因为要回到起点
	VALUE = 0.0;//适应度初始为0
	Q = 0.0;//累计适应度初始化为0
}
//计算该对象的路径距离
void Pop::Distance()
{
	int d = 0;//距离变量,初始化为0,后期计算累加
	for (int count = 0; count < 17; count++)//挨个遍历循环基因中每一个城市之间的距离
	{
		int max, min;
		min = GENE[count];
		max = GENE[count + 1];
		//你们或许这里看不懂,怎么解释呢这么说吧:
		//老师给出的城市距离是下三角矩阵,根据这个矩阵,可以找到从3到1的距离【第3行第1列】,但是找不到从1到3的距离
		//可以找到从5到3的距离,找不到从3到5的
		//也就是说,只能找到大城市到小城市的距离,小城市到大城市的距离就是0
		//但是我们的路径难免又从小到大,那怎么办呢
		//其中从5到3的距离就是从3到5的距离,所以我们这么处理
		//不管从大到小还是小到大,找到目前两个城市谁大谁小,然后就可以在第“大城市”行,第“小城市”列找到距离了
		//第166行和167行处,我们默认前面的是小城市,后面的是大城市
		if (GENE[count] > GENE[count + 1])//如果发现前大后小,那我们换个位置就行
		{
			max = GENE[count];
			min = GENE[count + 1];
		}
		//下方一行为路径计算输出测试
		//cout << "from " << max << " to " << min << " is: " << map[max-1][min-1] << endl;
		d += map[max - 1][min - 1];//距离累加,注意不要忘了,数组起始是从0而不是1开始的,所以行列数字要减1
	}
	DISTANCE = d;//最后把距离赋值给DISTANCE
	//cout << DISTANCE << endl;
}
//输出路径距离
void Pop::Show_Distance()
{
	cout << "Distance: " << DISTANCE << endl;
}
//依次展示所有路径的距离
void Population::Population_Show()
{
	//cout << "shows" << endl;
	//cout << N << endl;
	for (int count = 0; count < N; count++)//通过循环调用Pop类的各种show成员函数,输出N个路径的信息
	{
		//cout << "show" << endl;
		P[count].Show();//输出路径
		P[count].Show_Distance();//输出距离
		P[count].Show_Value();//输出适应度
		P[count].Show_Q();//输出累计适应度
		cout << endl << endl;
	}
}
//依次展示所选择出来的即将交叉的路径的信息
void Population::Population1_Show()
{
	cout << "以下是初代中被选择出来的P1代:" << endl << endl;
	for (int count = 0; count < (2*(N/4)); count++)
	{//这里要选择N/4对个体,也是是2*(N/4)个个体,为什么不直接N/2,是为了防止出现N为特殊数字的情况
		//当初代种群数量为偶数,如20个,N/2=10个,2*(N/4)=10个,没问题
		// 当初代种群数量为奇数,如7个,N/2=3个,2*(N/4)=2个,不一样,而选择3个个体,无法进行两两交叉
		//cout << "show" << endl;
		P1[count].Show();
		P1[count].Show_Distance();
		P1[count].Show_Value();
		P1[count].Show_Q();//通过循环调用Pop类的各种show成员函数,输出N个路径的信息
		cout << endl << endl;
	}
}
void Population::Population2_Show()
{
	cout << "以下是进行交叉后产生的P2子代信息:" << endl;
	for (int count = 0; count < P2_NUMBER; count++)//循环输出
	{
		//cout << "show" << endl;
		P2[count].Show();//只输出路径,因为其仅进行路径交叉,DISTANCE等值还没计算呢,输出没意义
		cout << endl << endl;
	}
}
//路径组类对象的初始化函数,参数n为需要构造的路径数量
Population::Population(int n)
{
	N = n;//将输入参数n赋值给类内部存储种群数量的变量N里
	DISTANCEP = 0;//总距离初始化为0
	P2_NUMBER = 0;//P2子代数量初始化为0
	for (int count = 0; count < n; count++)
	{
		P[count].Pop_Init();//通过循环对每一个子代,调用其自身的初始化函数进行初始化
	}
	for (int line = 0; line < CITY_NUMBER; line++)
	{//这个for循环就是对我们即将写入的城市距离二维矩阵进行0赋值初始化
		int temp;
		for (int row = 0; row < CITY_NUMBER; row++)
		{
			map[line][row] = 0;
		}
	}
	FILE* F;
	if ((F = fopen("C:\\Users\\cyxue\\Desktop\\TSP\\gr17.txt", "rb")) == NULL)//若读取指定城市距离矩阵文件失败
	{
		cout << "无法读取文件,请检查项目文件路径中有无该文件" << endl;
		exit(0);//终止程序并报错
	}
	for (int line = 0; line < CITY_NUMBER; line++)//距离转存数组的双层行列for循环部分
	{
		for (int row = 0; row < (line + 1); row++)
		{
			fscanf(F, "%d", &map[line][row]);//从文件中依次读取距离数据,并转存入map二维数组中
		}
	}
	//下方为距离下三角矩阵输出测试函数
	/*for (int line = 0; line < CITY_NUMBER; line++)
	{
		for (int row = 0; row < CITY_NUMBER; row++)
		{
			cout << map[line][row] << " ";
		}
		cout << endl;
	}*/
}
//计算路径组每一个路径的距离
void Population::DistanceP()
{
	for (int count = 0; count < N; count++)//挨个调用Pop类的相关函数进行每一个路径的距离计算
	{
		P[count].Distance();
	}

}
//显示路径组每一个路径的距离
void Population::Show_DistanceP()
{
	for (int count = 0; count < N; count++)
	{
		P[count].Show_Distance();
	}
}
//计算路径组中每一个路径的适应度:Distance/总Distance
void Population::Got_ValueP()
{
	for (int count = 0; count < N; count++)//先计算种群的总距离,并存入DISTANCEP中
	{
		DISTANCEP += P[count].DISTANCE;
	}
	for (int count = 0; count < N; count++)//然后再计算每一个路径的适应度VALUE,为了保险强制转换为double类型
	{
		P[count].VALUE = (double)P[count].DISTANCE / DISTANCEP;
	}

}
//计算路径组中每一个路径的累计适应度
void Population::Got_Q()
{
	double r;
	for (int count = 0; count < N; count++)//计算每一个路径的Q
	{
		r = 0.0;//最终结果暂存变量,每次新的路径进行计算都初始化为0
		for (int i = 0; i <= count; i++)//从头到该路径所有的VALUE值之和就是Q
		{
			r += P[i].VALUE;
		}
		P[count].Q = r;//把最终结果赋值给该路径的成员变量Q
	}
}
//获取当前条件下的Pop数组的起始指针
void Population::Pointer()
{
	begin = &P[0];//获取初代P种群的首元素地址
	end = &P[N + P2_NUMBER];//获取初代种群P的末尾元素地址
	//有人问为什么要加P2_NUMBER啊?
	//P2_NUMBER是交叉产生的后代的数量
	//P2代后期是要加入到P种群中作为下一次迭代的初始种群的
	//可是交叉产生P2前后我们都需要获得这个指针地址,怎么办呢?
	//没事,没交叉前,P2_NUMBER=0,交叉后才会有值,不用担心
}
//精英保留法选择N/2对个体P1或者产生下次迭代的初始种群P
void Population::Select(int number,int type)//精英保留法
{//可能很多教程都选择了什么轮盘赌之类的,我这里选择精英保留法:保留所有个体中距离最短的前2*number个
	sort(begin, end, From);//利用C++的sort()函数进行排序,begin与end指针传入,From()函数确定升序排序
	if (type == 0)//type=0,即为产生P1子代,选出前2*number个
	{
		//cout << "  --type 0:待选种群数量:" << number * 2 << endl;
		for (int count = 0; count < (2 * number); count++)
		{
			if(P[count].DISTANCE!=0)//我们之前初始化不是有大量空白元素DISTANCE为0吗,我们排除出去,选择距离非零的
				P1[count] = P[count];
		}
	}
	else if (type == 1)//type=1,则为生成下一次迭代所用的初始种群,即P+P2中的距离非零最短的前2*number个个体
	{
		//cout << "  --type 1:待选种群数量:" << number * 2 << endl;
		class Pop temp[2000];//临时存储数组
		for (int count = 0; count < N; count++)//此时P在经过main函数中的处理,已经把P2子代加入到P中了
		{//所以我们直接选出P的前2*number名,就是P+P2的前2*number名
			if (P[count].DISTANCE != 0)//选择距离非零的
				temp[count] = P[count];
		}
		for (int count = 0; count < (N+P2_NUMBER); count++)
		{
			P[count].DISTANCE = 1000000.0;
			P[count].VALUE = 0.0;
			P[count].Q = 0.0;//把P代的存储数组全部清空,VALUE和Q清零,DISTANCE也设置为任何一个数,0也不错
			for (int index = 0; index < (CITY_NUMBER + 1); index++)
			{
				P[count].GENE[index] = 0;//基因也清零
			}
		}
		for (int count = 0; count < N; count++)
		{
			P[count] = temp[count];//通过循环,把暂存到temp数组的前2*number名存入P中,作为下一次迭代的初始种群
		}
	}
}
//处理单对路径,进行指定位置的交叉互换
void Population::Crosser(class Pop p1, class Pop p2, int index)
{
	//我们采用部分交叉的方式,通俗的说就是这样
	//保留p1的前段和后段,保留p2的前段和后段
	//把p1中段交换给p2产生新的子代
	//把p2中段交换给p1也产生新的子代
	int temp[6];//存储各个对象中段6位基因的临时数组
	for (int count = 0; count < 6; count++)
	{
		temp[count] = p1.GENE[count + 5];//暂存p1的中段6位,从第5~第10位
	}
	for (int count = 0; count < 6; count++)//把p2中间6位覆盖到p1对应中间位置,也是5~10位
	{
		p1.GENE[count + 5] = p2.GENE[count + 5];
	}
	for (int count = 0; count < 6; count++)//把temp中暂存的p1中间段覆盖到p2中间位置上
	{
		p2.GENE[count + 5] = temp[count];
	}
	P2[index] = p1;
	P2[index + 1] = p2;//把经过交叉变换后的新一代存到P2数组中
}
//对于P2中的每一对路径,进行指定基因段的交叉
void Population::Tran()
{
	P2_NUMBER = 0;//P2代数量初始为0
	int r2_index = 0;//变量,指示并记录着当前交叉产生的P2子代的数组位置
	double p[NN/4];//存储专门产生的每一对专属的随机数
	for (int count = 0; count < (N / 4); count++)
	{
		p[count] = (double)rand() / RAND_MAX;//产生随机数中
	}
	for (int count = 0; count < N / 4; count++)//对每一对P1进行判断
	{
		if (p[count] < PC)//若对应随机数小于PC,则交叉变异
		{
			//cout<<p[count]<<" < "<<PC<<" Pair."<<count<<endl;
			Crosser(P1[2 * count], P1[2 * count + 1], r2_index);//调用Crosser函数进行两个路径对象基因的部分片段交叉
			r2_index += 2;//P2代数组存储位置加2
			P2_NUMBER += 2;//P2代数量加2,因为每一对交叉就会产生两个新一代
		}
	}
}
//将新的后代加入到初始种群中
void Population::Add()
{
	int i = 0;
	for (int count = N; count < (N + P2_NUMBER); count++, i++)
	{//把P2数组中存储的新一代接到P初始种群数组中
		P[count] = P2[i];
	}
	//NUMBER+=R2_NUMBER;
}
//对于Distance,value,Q,Pointer三个值计算的归纳函数
void Population::Canculate()
{//分别调用相关函数即可
	DistanceP();
	Got_ValueP();
	Got_Q();
	Pointer();
}
int main()
{
	srand(time(NULL));//C++产生随机数的必要语句,产生随机数种子,可以百度。本程序中用来产生随机小数判断变异与否
	class Population PS(NN);//定义路径组种群类的几个对象,且传入参数,产生NN个初始个体,NN可在宏定义修改
	int count = 0;//循环控制变量
	while (count < ROUND)
	{
		//cout << "第"<<count<<"代处理中:" << endl;
		PS.Canculate();//计算初始种群各项数值
		//PS.Population_Show();
		PS.Select(PS.N / 4, 0);//选择种群中距离最短的前PS.N/4对
		//cout << "初始种群选择结果显示:" << endl;
		//PS.Population1_Show();
		PS.Tran();//对选择出来的那几对个体进行基因部分交叉
		//cout << "选择目标交叉结果显示:" << endl;
		//PS.Population2_Show();
		//cout << "-------" << endl << "种群连接结果:" << endl;
		PS.Add();//将交叉产生的P2子代加入P初始种群中
		PS.Canculate();//计算当前初始P种群中,新加入的P2代的各项数值
		PS.Select(PS.N / 4, 1);//从P和P2代中选出距离最短的前几名
		count++;
		}
		PS.Canculate();//将最后一次循环后的种群数值再计算一遍
		cout << "--------------------" << endl;
		//PS.Population_Show();
		for (int count = 0; count < PS.N; count++)//从头开始找
		{//因为排了序,所以数组越前距离越短
			if (PS.P[count].DISTANCE != 0)//直到找到第一个距离非0的距离最短的个体
			{
				cout <<CITY_NUMBER<<"个城市的 最佳路径为:" << endl;
				PS.P[count].Show();
				cout << endl << "距离为:" << PS.P[count].DISTANCE << endl;//输出路径与距离信息,跳出循环
				break;
			}
		}
		return 0;
	
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值