TSP问题,贪心法,最近邻点,最短链接

    笔者接着上一次的博客继续讨论TSP问题(TSP问题,动态规划法),这次采用贪心法,至少有两种贪心策略是合理的:最近邻点策略和最短链接策略。

    (一)最近邻点策略

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

《TSP问题,贪心法,最近邻点,最短链接》

    设图G有n个顶点,边上的代价存储在二维数组w[n][n]中,集合V存储图的顶点,集合P存储经过的边,最近邻点策略求解TSP问题的算法如下:

   1. P={ };     
   2. V=V-{u0}; u=u0;   //从顶点u0出发
   3. 循环直到集合P中包含n-1条边
          3.1  查找与顶点u邻接的最小代价边(u, v)并且v属于集合V;
          3.2  P=P+{(u, v)};
          3.3  V=V-{v};
          3.4  u=v;   //从顶点v出发继续求解

    代码实现如下:

#include <iostream.h>
#include<fstream> 
const int amount = 5;
int TSP1(int arc[amount][amount], int temp)
{
	int Count_hyh= 0, TSPLen_hyh = 0;
	int min_hyh, u, v;
	int tem[amount] = {0};  
	u = temp; tem[temp] = 1;
	while (Count_hyh< amount-1)
	{
		min_hyh  = 100;
		for (int j = 0; j < amount; j++)
			if ((tem[j] == 0) && (arc[u][j] != 0) && (arc[u][j] < min_hyh)) {
				v = j;
				min_hyh = arc[u][j];
			}
		TSPLen_hyh += arc[u][v];
		tem[v] = 1; Count_hyh++;
		cout<<u<<"-->"<<v<<endl;
		u = v;
	}
	cout<<v<<"-->0"<<endl; 
	return (TSPLen_hyh + arc[v][0]);
}
int main( )
{
	int arc[amount][amount] = {{999,3,3,2,6},{3,999,7,3,2},{3,7,999,2,5},{2,3,2,999,3},{6,2,5,3,999}};
	int min_hyhDist = TSP1(arc, 0);
	cout<<"最短长度:"<<min_hyhDist<<endl;
	return 0;
}

    (二)最短链接策略

    每次在整个图的范围内选择最短边加入到解集合中,但是,要保证加入解集合中的边最终形成一个哈密顿回路。因此,当从剩余边集E’中选择一条边(u, v)加入解集合S中,应满足以下条件:① 边(u, v)是边集E’中代价最小的边;② 边(u, v)加入解集合S后,S中不产生回路;③ 边(u, v) 加入解集合S后,S中不产生分枝;

    《TSP问题,贪心法,最近邻点,最短链接》

    设图G有n个顶点,边上的代价存储在二维数组w[n][n]中,集合E’是候选集合即存储所有未选取的边,集合P存储经过的边,最短链接策略求解TSP问题的算法如下:

    1.P={ };     
    2.E'=E;     //候选集合,初始时为图中所有边
    3.循环直到集合P中包含n-1条边
          3.1 在E'中选取最短边(u, v);
          3.2  E'=E'-{(u, v)};
          3.3 如果 (顶点u和v在P中不连通 and 不产生分枝) 
                则P=P+{(u, v)};

    实现上述伪代码的难点在于判断u和v在P中是否连通,笔者解决的办法是采用并查集。用一个数组实现:int father[N];,其中father[i]表示元素i的父亲节点,而父亲节点本身也是这个集合内的元素,father[1]=2就表示元素1的父亲结点是元素2,以这种父亲关系来表示元素所属的集合。若father[i]=i,则说明元素i是该集合的根节点。并查集的基本操作包括:初始化、查找、合并。对于初始化,一开始每个元素都是独立的一个集合,则令所有的i的father[i]=i;查找就是给定对给定的节点寻找其根结点的过程,即通过递推或者递归反复寻找父亲结点;合并就是把两个集合合为一个,先判断它们是否属于同一集合,再选择是否合并。当然,还可以压缩路径,即把当前查询结点的路径上的所有结点的父亲都指向根节点。

    实现代码如下:

#include<stdio.h>
#include<algorithm>
using namespace std;
#define N 50
typedef struct point
{
	int value; 
	int from;  
	int end; 
}point; 
int point_set[N];  
int radio[N];
point result[N];  
int Find(int x)
{
 
    int root,parent; 
    root = x;
    while(point_set[root] != root)
        root = point_set[root]; 
    parent = point_set[x]; //路径压缩 
    while(root != parent)
    { 
        point_set[x] = root;
        x = parent;
        parent = point_set[x];     
	}
    return root; 
}
int Union(int a,int b)
{ 
    int root_a = Find(a);
    int root_b = Find(b);
    point_set[root_b] = root_a; 
    return root_a; 
}
bool com_point(point a,point b)
{ 
	return a.value < b.value;
}
int  TSP_Short(int dis[][N],int n,point point[])
{ 	
	int temp = (n*(n-1))/2;   // 边的个数
	int i;
	int count = 0;
	int k = 0;
	for(i = 0; i < temp; i++)
		if(point[i].value != 0)
		{
			if((Find(point[i].from) != Find(point[i].end))&&radio[point[i].from] != 2 && radio[point[i].end] != 2)
			{		  
				count += point[i].value;
				radio[point[i].from]++;
				radio[point[i].end]++;
				result[k].value = point[i].value;
				result[k].from = point[i].from;
				result[k].end = point[i].end;
				Union(point[i].from,point[i].end);
				k++;
				if(k == n -1)
					break;
			}
 
		}
	return count; 
}
int main()
{
	int n; 
	printf("请输入顶点个数:");
	scanf("%d",&n);
	int i,j;
	int dis[N][N]; 
	printf("输入%d阶对称矩阵:\n",n); 
	for(i = 1; i <= n; i++)
		for(j = 1; j <= n; j++)
			scanf("%d",&dis[i][j]);
	point point[N]; 
	int cost  = 0; 
	int t = 0;
	for(i = 1; i <= n; i++)
		for(j = 1; j <= i-1; j++)
		{
			point[t].value = dis[i][j];
			if(i < j)
			{
		 		point[t].from = i;
				point[t].end = j;
			}
			else
			{
		 		point[t].from = j;
				point[t].end = i;
	 
			}
		t++;	 
	}
	sort(point,point+t,com_point);
	for(i = 1; i <= n; i++)
		point_set[i] = i;
	memset(radio,0,sizeof(radio));
	cost = TSP_Short(dis,n,point);
	int end[2]; 
	int x = 0;
	for(i = 1; i <= n; i++)
	{	 
		if(radio[i] == 1)
		end[x++] = i;	 
	}
	printf("边的两个顶点:");
	for(i = 0; i < n-1; i++)
		printf("v%d--v%d\t%d\n",result[i].from,result[i].end,result[i].value);
	printf("v%d--v%d\t%d\n",end[0],end[1],dis[end[0]][end[1]]); 
	printf("%d\n",cost+dis[end[0]][end[1]]);
	return 0;
}

以上就是TSP的贪心法求解,笔者还将对TSP用回溯法求解,感兴趣的可以关注笔者博客。

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