ACS解决TSP问题的简单实现(C++)

一、背景

1.1、TSP问题

  • 旅行商问题,是数学领域中著名问题之一,假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。
  • V = { a , … , z } V=\{a, \ldots ,z\} V={a,,z}表示城市集合, A = { ( r , s ) : r , s ∈ V } A=\{ (r,s):r,s\in V \} A={(r,s):r,sV}表示边的集合,同时 δ ( r , s ) = δ ( s , r ) \delta (r,s)=\delta(s,r) δ(r,s)=δ(s,r)表示边 ( r , s ) (r,s) (r,s)上的距离成本。例如,城市 r ∈ V r \in V rV的坐标为 ( x r , y r ) (x_r,y_r) (xr,yr) δ ( r , s ) \delta (r,s) δ(r,s)表示城市 r r r s s s之间的欧几里得距离。
  • TSP问题的一些数据:http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp/

1.2、蚁群算法

  • Ant System模仿自然界中的蚂蚁觅食的行为。在两个城市 r r r s s s间不仅有距离成本 δ ( r , s ) \delta(r,s) δ(r,s),而且有信息素的记录 τ ( r , s ) \tau (r,s) τ(r,s)。每只蚂蚁通过probabilistic state transition rule来选择城市以完成自己的旅程;当所有蚂蚁完成了自己的旅程后,global pheromone updating rule开始应用,然后不断地迭代以上过程。
  • Ant Colony System是在Ant System上做的改进,主要表现在以下三个方面:the state transition rule为蚂蚁提供了探索新边和基于原先经验(信息素)进行选择的平衡;the global updating rule只针对最优路径进行更新;当蚂蚁完成一次旅程后,用local pheromone updating rule进行更新。

二、算法的主要内容

2.1、ACS State Transition Rule(状态转换,伪随机比例规则)

在城市 r r r上的蚂蚁通过该规则选择下一个城市 s s s s = { arg ⁡ max ⁡ u ∈ J k ( r ) { [ τ ( r , u ) ] ⋅ [ η ( r , u ) ] β } , if  q ≤ q 0 (exploitation) S , otherwise(biased exploration) s= \begin{cases}\arg \max_{u\in J_k(r)}\{[\tau(r,u)]\cdot[\eta(r,u)]^{\beta}\}, & \text {if $q\leq q_0$(exploitation)} \\ S, & \text{otherwise(biased exploration)} \end{cases} s={argmaxuJk(r){[τ(r,u)][η(r,u)]β},S,if qq0(exploitation)otherwise(biased exploration) q q q是均匀分布, q 0 q_0 q0是一个参数, S S S是随机从以下分布(给出在城市 r r r的蚂蚁 k k k选择去城市 s s s的概率)中采样的值, p k ( r , s ) = {   [ τ ( r , s ) ] ⋅ [ η ( r , s ) ] β ∑ u ∈ J k ( r ) [ τ ( r , u ) ] ⋅ [ η ( r , u ) ] β   if  s ∈ J k ( r ) 0 , otherwise p_k(r,s)= \begin{cases}\ \frac{[\tau(r,s)]\cdot[\eta(r,s)]^{\beta}}{\sum_{u\in J_k(r)}[\tau(r,u)]\cdot[\eta(r,u)]^\beta}\, & \text {if $s\in J_k(r)$} \\ 0, & \text{otherwise} \end{cases} pk(r,s)= uJk(r)[τ(r,u)][η(r,u)]β[τ(r,s)][η(r,s)]β0,if sJk(r)otherwise

2.2、ACS Global Updating Rule(全局更新)

在ACS中,仅允许最短行程的蚂蚁存放信息素,故全局更新规则如下, τ ( r , s ) = ( 1 − α ) ⋅ τ ( r , s ) + α ⋅ Δ τ ( r , s ) \tau(r,s)=(1-\alpha)\cdot\tau(r,s)+\alpha \cdot \Delta \tau(r,s) τ(r,s)=(1α)τ(r,s)+αΔτ(r,s)其中, Δ τ ( r , s ) = { ( L g b ) − 1 if ( r , s ) ∈ global-best-tour 0 otherwise \Delta \tau(r,s)=\begin{cases}(L_{gb})^{-1}&\text {if$(r,s)\in$global-best-tour} \\ 0&\text{otherwise}\end{cases} Δτ(r,s)={(Lgb)10if(r,s)global-best-tourotherwise L g b L_{gb} Lgb表示从实验开始后的全局最佳路径, 0 < α < 1 0<\alpha<1 0<α<1是一个参数

2.3、ACS Local Updating Rule(局部更新)

每只蚂蚁在游览途中,通过应用局部更新规则来访问边并更改信息素水平 τ ( r , s ) = ( 1 − ρ ) ⋅ τ ( r , s ) + ρ ⋅ Δ τ ( r , s ) \tau(r,s)=(1-\rho)\cdot \tau(r,s)+\rho \cdot \Delta \tau(r,s) τ(r,s)=(1ρ)τ(r,s)+ρΔτ(r,s)其中 0 < r < 1 00<r<1是一个参数, Δ τ ( r , s ) = τ 0 \Delta \tau(r,s)=\tau_0 Δτ(r,s)=τ0

三、实现

3.1、参数定义、Ant类和全局变量

//主要是规则公式中的一些常量
#define m 10//蚂蚁数量为10
#define alpha 0.1//α,为0.1
#define beta 2
#define q0 0.9
#define rho 0.1//ρ
//蚂蚁类
class ant{
public:
	int localcity;//该蚂蚁的当前城市
	int nextcity;//该蚂蚁的下一个城市
	vector<int> tour;//该蚂蚁的路径,每到一个城市就将这个城市push_back到tour里
	set<int> unvisited;//存储该蚂蚁未访问的城市,使整个solution符合现实情况
	int length;//对应tour的长度
};
ant ants[m+1];//蚁群,蚂蚁1到蚂蚁m
//全局变量
const int citys_num=76;//城市数量
double citys_x[citys_num+1];//存放城市的x坐标
double citys_y[citys_num+1];//存放城市的y坐标
int D[citys_num+1][citys_num+1];//距离矩阵,D[i][j]存放城市i到城市j的距离
double tao[citys_num+1][citys_num+1];//τ,信息素矩阵
double tao0;//τ0=(n*Lnn)^(-1)
vector<int> tour_gb;//全局最佳路径
int tour_length_gb= INT_MAX;//全局最佳路程,初始化为无穷大

3.2、读取TSP数据

没有实现对所有格式文件的读入,具体读入需要根据问题的数据格式修改函数。
例如对kroA100,先跳过前六行,之后逐行读入每个城市的编号、x坐标、y坐标,然后计算距离矩阵
ACS解决TSP问题的简单实现(C++)_第1张图片

//读取TSP文件中的TSP问题的城市数据
void ReadTSPData(){
	ifstream infile;
	infile.open("D:\\2019SRP\\TSP数据\\ALL_tsp\\eil76.tsp\\eil76.tsp");
	for(int i=0;i<6;i++){
		string trash;
		getline(infile,trash);
	}//过滤掉前六行的文件信息
	
	//开始读入有用数据
	for(int i=1;i<=citys_num;i++){
		int t1;
		double t2,t3;
		infile>>t1>>t2>>t3;
		citys_x[i]=t2;citys_y[i]=t3;//存入城市的x、y值、
	}

	//计算城市间的距离矩阵
	for(int i=1;i<=citys_num;i++){
		for(int j=i+1;j<=citys_num;j++){
			D[i][j]=round_double(sqrt((citys_x[i]-citys_x[j])*(citys_x[i]-citys_x[j])+(citys_y[i]-citys_y[j])*(citys_y[i]-citys_y[j])));
			D[j][i]=D[i][j];//对称TSP
		}
		D[i][i]=0;//自己到自己的距离为0
	}
}

3.3、计算 τ 0 \tau_0 τ0

初始化蚂蚁A,设其起点为城市1,对其应用就近原则,然后通过得到的路程计算 τ 0 \tau_0 τ0

void Calcu_tao0(){
	double Lnn=0;
	ant A;//通过蚂蚁A来计算出Lnn,Lnn标识按照优先选取最近城市的原则对所有城市做遍历后得到的一个回路的长度
	A.length=0;A.localcity=0;A.tour.clear();A.unvisited.clear();
	for(int i=1;i<=citys_num;i++){A.unvisited.insert(i);}
	//初始化蚂蚁A
	
	A.localcity=1;A.tour.push_back(1);A.unvisited.erase(1);//设蚂蚁A的起点为城市1(?)

	//对蚂蚁A Build_Tour(最近原则)
	for(int i=2;i<=citys_num;i++){

		auto iter=A.unvisited.begin();
		int nearest_city= *iter;
		double nearest_len=D[A.localcity][nearest_city];
		while(iter!=A.unvisited.end()){
			if(D[A.localcity][*iter]<nearest_len){
				nearest_city=*iter;
				nearest_len=D[A.localcity][*iter];
			}
			iter++;
		}
		//至此,找出了在A.unvisited集合中离A.localcity最近的城市nearest_city和到其的路程nearest_len

		A.length+=nearest_len;A.localcity=nearest_city;A.tour.push_back(nearest_city);A.unvisited.erase(nearest_city);
		//将nearest_city加入到A的tour中
	}

	A.tour.push_back(1);A.length+=D[A.localcity][1];A.localcity=1;//蚂蚁A需要回到起始城市1

	Lnn=A.length;
	tao0=1/(citys_num*Lnn);//计算出tao0
}

3.4、初始化工作

//初始化信息素矩阵
void Initial_tao(){
	Calcu_tao0();//计算出τ0

	//初始化信息素矩阵
	for(int i=1;i<=citys_num;i++){
		for(int j=1;j<citys_num;j++){
			//初始化τ为τ0
			tao[i][j]=tao0;
		}
	}
}
//初始化蚂蚁
void Initial_ants(){
	//对每只蚂蚁清空之前记录,主要的有清空tour,对unvisited填满
	for(int k=1;k<=m;k++){
		ants[k].localcity=0;
		ants[k].tour.clear();
		ants[k].unvisited.clear();
		for(int i=1;i<=citys_num;i++){
			ants[k].unvisited.insert(i);
		}
		ants[k].length=0;
	}

	//蚂蚁被随机放置,每个城市最多放置一只蚂蚁
	vector<int>citys_noants;//没有蚂蚁的城市
	for(int i=1;i<=citys_num;i++){
		citys_noants.push_back(i);
	}//一开始城市都没有蚂蚁
	for(int k=1;k<=m;k++){
		int Rand=rand()%citys_noants.size();
		int c=citys_noants[Rand];//在没有蚂蚁的城市中随机取一
		ants[k].tour.push_back(c);//推入蚂蚁的tour
		ants[k].localcity=c;//if(k==5){cout<<"c:"<
		ants[k].unvisited.erase(c);//蚂蚁的unvisited中删除
		citys_noants.erase(citys_noants.begin()+Rand);//从没有蚂蚁的城市集合中剔除
	}
}

3.5、一个判断 ( r , s ) (r,s) (r,s)是否位于当前全局最佳路径中的函数

//判断(r,s)是否位于当前全局最佳路径中
bool IfInL_gb(int startcity,int endcity){
	int i;
	//全局最佳路径被保存在tour_gb和tour_length_gb中
	for(i=0;i<tour_gb.size()-1;i++){
		if(tour_gb[i]==startcity)break;
	}
	if(endcity==tour_gb[i+1]){return true;}
	else{return false;}
}

3.6、全局更新

//全局更新
void global_update(){
	//遍历当前迭代所有蚂蚁的最佳路程,如果有比全局最佳路程更好的,则替代
	for(int k=1;k<=m;k++){
		if(ants[k].length<tour_length_gb){
			tour_length_gb=ants[k].length;
			tour_gb.assign(ants[k].tour.begin(),ants[k].tour.end());
		}
	}

	//应用全局更新规则
	for(int i=1;i<=citys_num;i++){
		for(int j=1;j<=citys_num;j++){
			if(IfInL_gb(i,j)){
				tao[i][j]=(1-alpha)*tao[i][j]+alpha*(1/tour_length_gb);//如果属于全局最佳路径
			}else{
				tao[i][j]=(1-alpha)*tao[i][j];//不属于
			}
		}
	}
}

3.7、每只蚂蚁都完成路程

void Build_Tour(){
	for(int i=1;i<= citys_num;i++){//step循环
		//决定每个蚂蚁的下一城市
		if(i<citys_num){//状态转换——伪随机比例规则
			for(int k=1;k<=m;k++){//对于每只蚂蚁
				double q=rand()%1000/(double)1000;
				if(q<=q0){//exploitation
					double pheromone_closeness_max=-1;//要找出最大的[τ(r,u)][η(r,u)]^β
					int r=ants[k].localcity;
					int s=0;
					set<int>::iterator iter=ants[k].unvisited.begin();
					double pheromone_closeness=0;
					while(iter!=ants[k].unvisited.end()){
						int u=*iter;
						pheromone_closeness= tao[r][u]*(pow((double)1/D[r][u],(double)beta));
						//if(D[r][u]==0){cout<<"D[r][u]:"<
						if(pheromone_closeness>pheromone_closeness_max){
							s=u;
							pheromone_closeness_max=pheromone_closeness;
						}
						iter++;
					}
					//if(s==0){cout<<"s:"<
					ants[k].nextcity=s;//找出能使[τ(r,u)][η(r,u)]^β最大的u
					
				}else{//biased exploration

					double pheromone_closeness_sum=0;
					int r=ants[k].localcity;
					set<int>::iterator iter=ants[k].unvisited.begin();
					while(iter!=ants[k].unvisited.end()){
						int u=*iter;
						double pheromone_closeness= tao[r][u]*(pow((double)1/D[r][u],(double)beta));
						pheromone_closeness_sum+=pheromone_closeness;
						iter++;
					}
					double Rand=rand()%1000/(double)1000;
					double temp=0;iter=ants[k].unvisited.begin();
					int s=0;
					while(temp<=Rand){
						int u=*iter;
						double pheromone_closeness = tao[r][u]*(pow((double)1/D[r][u],(double)beta));
						temp+=pheromone_closeness/pheromone_closeness_sum;
						iter++;
						s=u;
					}
					ants[k].nextcity=s;//从random-proportional rule分布(1)中选择的S
				}

				//至此,对于蚂蚁k(它的当前城市为r),根据伪随机比例规则,从jk(r)中选出了nextcity s。

				//if(ants[k].nextcity==0){cout<<"is 0"<
				ants[k].tour.push_back(ants[k].nextcity);
				ants[k].length+=D[ants[k].localcity][ants[k].nextcity];
				ants[k].unvisited.erase(ants[k].nextcity);
				ants[k].localcity=ants[k].nextcity;
			}

		}else{//这时所有城市遍历完,返回起点
			//cout<<12121<
			for(int k=1;k<=m;k++){//对于每只蚂蚁
				//令该蚂蚁返回到原始起点,即下一城市为起点
				ants[k].nextcity=ants[k].tour[0];

				ants[k].tour.push_back(ants[k].nextcity);
				ants[k].length+=D[ants[k].localcity][ants[k].nextcity];
				ants[k].localcity=ants[k].nextcity;
			}
		}

		for(int k=1;k<=m;k++){
			//应用局部更新规则更新信息素
			int r=ants[k].tour[ants[k].tour.size()-2];
			int s=ants[k].tour[ants[k].tour.size()-1];
			tao[r][s]=(1-rho)*tao[r][s]+rho*tao0;
		}

	}
}

3.8、主函数

int main(){
	ReadTSPData();//读取TSP数据
	Initial_tao();//初始化信息素矩阵
	for(int it=0;it<500;it++){//iteration循环
		Initial_ants();//初始化蚂蚁
		Build_Tour();//每只蚂蚁建立自己的Solution
		global_update();//全局更新
		cout<<tour_length_gb<<endl;//输出经过每次迭代后的全局最佳路径的长度
	}
	cout<<"======================================"<<endl;
	cout<<"Result:"<<endl;
	for(int i=0;i<tour_gb.size();i++){
		cout<<tour_gb[i]<<" ";
	}cout<<endl;
	//输出全局最佳路径
}

四、问题

这个C++实现能基本遵循ACS的各个步骤,同时运行后也可以看出最佳路径的长度是再不断缩小的,但是仍存在以下问题:

  • 经过很多次迭代之后,依然找不到TSP问题的最佳答案(很接近)
  • 我按照TSP问题答案中的解决路径计算路径长度,与答案的长度不一致,而且TSP问题中有要求解决路径都得从城市1开始吗?
  • 对于 τ 0 \tau_0 τ0的计算,应用就近原则时,我把起点定在了城市1,但是理论上起点不一样,计算出的 τ 0 \tau_0 τ0应该也不一样
  • ACS-3opt的实现

你可能感兴趣的:(算法)