贪心法求解TSP问题 C++

1.问题描述 

 

    TSP问题是指旅行家要旅行n个城市然后回到出发城市,要求各个城市经历且仅经历一次,并要求所走的路程最短。

2.算法思想

    贪心法求解TSP问题有两种贪心策略。

     1)最近邻点策略:从任意城市出发,每次在没有到过的城市中选择最近的一个,直到经过了所有的城市,最后回到出发城市。

    给定初始的城市a,寻找与其邻接的最短距离的城市b,记录二者之间的路径并增加总路径长度;下一次从城市b开始,寻找与b邻接的最短距离的城市,循环查找,直到找到n-1条路径(n为城市个数),最后加上终点回到起点的边即可。
 

     2)最短链接策略:每次在整个图的范围内选择最短边加入到解集合中,但是,要保证加入解集合中的边最终形成一个哈密顿回路。

    首先按照城市之间距离远近进行排列,从距离最近的两个城市开始,如果这两个城市不在一个联通分量中并且度数均小于等于2,那么记录二者之间的路径,将它们划分到一个联通分量并将度数增加1;然后从距离第二小的两个城市开始,重复上述操作直到记录的路径有n-1条,最后找到度数为1的两个城市,作为最后一条路径。
 

3.参考代码

最近邻点策略:

/*TSP问题采用贪心法的最近邻点策略*/
/*
	TSP问题是指旅行家需要旅行n个城市,要求各个城市经过且仅经过一次,
	然后回到出发城市,并且所走的路径最短。
*/
/*
	最近邻点策略:从任意城市出发,每次在没有到过的城市中选择最近的一个,
	直到经过了所有的城市,最后回到出发城市
*/

#include<iostream>
#include<string>
#include<iomanip>
using namespace std;
//类TSP
class TSP
{
	private:
		int city_number;	//城市个数
		int **distance;		//城市距离矩阵
		int start;		//出发点
		int *flag;		//标志数组,判断城市是否加入汉密顿回路
		int TSPLength;		//路径长度
	public:
		TSP(int city_num);	//构造函数
		void correct();		//纠正用户输入的城市距离矩阵
		void printCity();       //输出用户输入的城市距离
		void TSP1();		//贪心法的最近邻点策略求旅行商问题
};

//构造函数
TSP::TSP(int city_num)
{
	int i=0,j=0;
	int start_city;
	city_number=city_num;

	//初始化起点
	cout<<"请输入本次运算的城市起点,范围为:"<<0<<"-"<<city_number-1<<endl;
	cin>>start_city;
	start=start_city;

	//初始化城市距离矩阵
	distance=new int*[city_number];
	cout<<"请输入"<<city_number<<"个城市之间的距离"<<endl;
	for(i=0;i<city_number;i++)
	{
		distance[i]=new int[city_number];
		for(j=0;j<city_number;j++)
			cin>>distance[i][j];
	}

	//初始化标志数组
	flag=new int[city_number];
	for(i=0;i<city_number;i++)
	{
		flag[i]=0;
	}

	TSPLength=0;

}

//纠正用户输入的城市代价矩阵
void TSP::correct()
{
	int i;
	for(i=0;i<city_number;i++)
	{
		distance[i][i]=0;
	}
}

//打印城市距离
void TSP::printCity()
{
	int i,j;
	//打印代价矩阵
	cout<<"您输入的城市距离如下"<<endl;
	for(i=0;i<city_number;i++)
	{
		for(j=0;j<city_number;j++)
			cout<<setw(3)<<distance[i][j];
		cout<<endl;
	}
}


//贪心法的最近邻点策略求旅行商问题
void TSP::TSP1()
{
	int edgeCount=0;
	int min,j;
	int start_city=start;	//起点城市
	int next_city;			//下一个城市
	flag[start]=1;
	cout<<"路径如下"<<endl;
	while(edgeCount<city_number-1)//循环直到边数等于city_number-1
	{
		min=100;
		for(j=0;j<city_number;j++)//求当前距离矩阵的最小值
		{
			if((flag[j]==0) && (distance[start_city][j] != 0) && (distance[start_city][j] < min))
			{
				next_city=j;
				min=distance[start_city][j];
			}
		}
		TSPLength+=distance[start_city][next_city];
		flag[next_city] = 1;//将顶点加入汉密尔顿回路
		edgeCount++;
		cout<<start_city<<"-->"<<next_city<<endl;
		
		start_city=next_city;//下一次从next_city出发
	}

	cout<<next_city<<"-->"<<start<<endl;//最后的回边
	TSPLength+=distance[start_city][start];
	cout<<"路径长度为"<<TSPLength;//哈密尔顿回路的长度

}


//主函数
int main()
{
	cout<<"欢迎来到贪心法的最近邻点策略求旅行商问题,请输入城市个数";
	int city_number;
	while(cin>>city_number)
	{
		TSP tsp(city_number);	//初始化
		tsp.correct();			   //纠正输入的城市距离矩阵
		tsp.printCity();		   //打印输入的城市
		tsp.TSP1();			   //求解
		cout<<"-------------------------------------------------------"<<endl;
		cout<<"\n欢迎来到贪心法的最近邻点策略求旅行商问题,请输入城市个数";
	}

	return 0;
}

最短链接策略:

/*TSP问题采用贪心法的最短链接策略*/
/*
	TSP问题是指旅行家需要旅行n个城市,要求各个城市经过且仅经过一次,
	然后回到出发城市,并且所走的路径最短。
*/
/*
	最短链接策略:每次在整个图的范围内选择最短的边加入到解集合中,保证解集合中的边最终形成一个哈密尔顿回路
*/
#include<iostream>
#include<string>
#include<iomanip>
#include<algorithm>
using namespace std;
/*TSP类*/
class TSP
{
	public:
        int start_city;		                //起点城市
		int end_city;			//终点城市
		int distance;			//两者之间的距离	
		TSP(int sta,int end,int dis);	//构造函数
		TSP();				//默认构造函数
		void init(int sta,int end,int dis);	//初始化函数
		void printfInfo();			//打印信息	
};
/*函数原型声明*/
int cmp(TSP t1,TSP t2);							//定义排序规则
int TSP2(TSP *t,int city_num,int cities,TSP *process);  //最短链接求TSP
int find(int x,int *pre);						//寻找前置结点
void join(int x,int y,int *pre);					//合并结点
//构造函数
TSP::TSP(int sta,int end,int dis)
{
	start_city=sta;
    end_city=end;
	distance=dis;
}
//默认构造函数
TSP::TSP()
{
}
//初始化函数
void TSP::init(int sta,int end,int dis)
{
    start_city=sta;
    end_city=end;
	distance=dis;
}
//打印信息函数
void TSP::printfInfo()
{
	cout<<start_city<<"\t"<<end_city<<"\t"<<distance<<"\t\n";
}

//排序规则
int cmp(TSP t1,TSP t2)
{
	return t1.distance<t2.distance;
}

//最短链路求解TSP问题
int TSP2(TSP *t,int city_num,int cities,TSP *process)
{
	int TSPLength=0;
	int i=0,j=0,k=0;
	int *flag=new int[city_num];	//记录结点的度数
	int *pre=new int[city_num];		//记录结点的前置结点
	for(i=0;i<city_num;i++)
	{
		flag[i]=0;					//初始结点度数为0
		pre[i]=i;					//每个结点的前置结点设置为自己
	}
	do{
		for(i=0;i<cities;i++)
		{
			if(find(t[i].start_city,pre)!=find(t[i].end_city,pre))//如果边连接的点属于不同的集合
			{
				if(flag[t[i].start_city]<=2 && flag[t[i].end_city]<=2)//不会产生分支
				{
					flag[t[i].start_city]++;	//度数加1
				    flag[t[i].end_city]++;
					TSPLength+=t[i].distance;	//增加路径长度
					//保存路径
					process[k].start_city=t[i].start_city;
					process[k].end_city=t[i].end_city;
					process[k].distance=t[i].distance;
				//	cout<<"从城市"<<t[i].start_city<<"到城市"<<t[i].end_city<<",距离为"<<t[i].distance<<endl;
					j++;
					k++;
					join(t[i].start_city,t[i].end_city,pre);//两点所在的集合合并
				}
			}
		}
	}while(j<city_num-1);
	//找到度数为1的两个点
	int m,n;
	for(m=0;m<city_num;m++)
		if(flag[m]==1)
		{
			process[k].start_city=m;
			flag[m]++;
			break;
		}
	for(n=0;n<city_num;n++)
		if(flag[n]==1)
		{
			process[k].end_city=n;
			flag[n]++;
			break;
		}
	//找到最后一条边的长度
	for(i=0;i<cities;i++)
		if(t[i].start_city==m && t[i].end_city==n)
			process[k].distance=t[i].distance;
	TSPLength+=process[k].distance;
	return TSPLength;
}

//找前置结点
int find(int x,int *pre)
{
    int r=x;
    while(pre[r]!=r)
    r=pre[r];		//找到前导结点
    int i=x,j;
    while(i!=r)		//路径压缩算法
    {
        j=pre[i];	//记录x的前导结点
        pre[i]=r;	//将i的前导结点设置为r根节点
        i=j;
    }
    return r;
}

//合并结点
void join(int x,int y,int *pre)
{
    int a=find(x,pre);	//x的根节点为a
    int b=find(y,pre);	//y的根节点为b
    if(a!=b)			//如果a,b不是相同的根节点,则说明ab不是连通的
    {
        pre[a]=b;		//将ab相连 将a的前导结点设置为b
    }
}

//主函数
int main()
{
	cout<<"欢迎来到贪心法的最短链路策略求旅行商问题,请输入城市个数";
	int city_number,cities=0; 
	while(cin>>city_number)
	{
		int i,j,k=0;
		for(i=1;i<city_number;i++)
		   cities+=i;
		//声明
		TSP *tsp=new TSP[cities];			//记录所有城市的路径
		TSP *process=new TSP[city_number];	//最短链接求解的路径

	    //初始化代价矩阵
		cout<<"请输入城市的代价矩阵"<<endl;
		int **distance;
		distance=new int*[city_number];
		for(i=0;i<city_number;i++)
		{
			distance[i]=new int[city_number];
			for(j=0;j<city_number;j++)
			{
		     	cin>>distance[i][j];
			}
		}
		//生成对象数组 
		for(i=0;i<city_number;i++)
		{
		    for(j=i+1;j<city_number;j++)
			{
			   tsp[k].init(i,j,distance[i][j]);
			   k++;
			 }
		}
		//输出对象数组
		cout<<"起点\t"<<"终点\t"<<"距离\t"<<endl;
		for(i=0;i<cities;i++)
		{
		    tsp[i].printfInfo();	
		}
        cout<<"-------------------------------------"<<endl;
		//排序
		sort(tsp,tsp+cities,cmp);
		cout<<"\n排序后"<<endl;
		cout<<"起点\t"<<"终点\t"<<"距离\t"<<endl;
		for(i=0;i<cities;i++)
		{
		    tsp[i].printfInfo();	
		}
		cout<<"-------------------------------------"<<endl;
		//最短链接求解TSP问题
		cout<<"最短链接求解过程:"<<endl;
		int length=TSP2(tsp,city_number,cities,process);
		cout<<"起点\t"<<"终点\t"<<"距离\t"<<endl;
		for(i=0;i<city_number;i++)
		{
		    process[i].printfInfo();	
		}
		cout<<"路径长度:"<<length<<endl;
        delete[] tsp;
	}
	return 0;
}

 

4.参考结果

 

最近邻点:

 

《贪心法求解TSP问题 C++》

最短链路:

《贪心法求解TSP问题 C++》

5.补充

 

    并查集:通过前置结点来判断当前的结点是否属于一个联通分量,不属于则合并两个结点,合并的方法就是将其中一个结点的前置结点设置为另一个结点;当然还可以压缩路径,计算过程中记录结点的根结点,这样就不用依次向上遍历了。

    

    关于sort(),sort并不是简单的快速排序,它对普通的快速排序进行了优化,此外,它还结合了插入排序和推排序。系统会根据数据形式和数据量自动选择合适的排序方法,这并不是说每次排序只选择一种方法,是在一次完整排序中不同的情况选用不同方法,比如给一个数据量较大的数组排序,开始采用快速排序,分段递归,分段之后每一段的数据量达到一个较小值后它就不继续往下递归,而是选择插入排序,如果递归的太深,会选择推排序。

 

 

 

    原文作者:分支限界法
    原文地址: https://blog.csdn.net/shujian_tianya/article/details/80885029
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞