TSP问题-多种算法求解

TSP问题-多种算法求解目录前言问题及思路 1 问题概述 2 设计思路源码及测试 1 输入 2 代码前言算法大作业 综合应用 8 种算法解决 TSP 问题 分别是 蛮力法 顺序查找 分治法 快速排序 贪心法 求上界 近似算法 贪心

大家好,欢迎来到IT知识分享网。

前言

算法大作业,综合应用8种算法解决TSP问题,分别是:
蛮力法(顺序查找)
分治法(快速排序)
贪心法(求上界)
近似算法(贪心+寻找最优贪心值)
分支限界法(多城市)
动态规划法(少城市)
回溯法(中等规模城市数量)
Sherwood概率算法改进版(随机第一个城市)
共8种算法(加粗的用于求解问题
第一次发博客,如有错误,希望大佬们指正!

问题及思路

 

1.问题概述

TSP问题是指旅行家要旅行n个城市,要求每个城市经历一次且仅经历一次然后回到出发城市,并要求所走路程最短。其本质就是图论中给定一个加权完全图(顶点表示城市,边表示道路,权重是道路的成本或距离),求一个权值最小的哈密尔顿回路。
在这里插入图片描述
 

2.设计思路

综合了每种算法的优点,具体如下图
在这里插入图片描述
 

源码及测试

 

1.输入

输入城市数量,然后输入城市间的邻接矩阵,其中自身到自身为无穷大。示例如下
在这里插入图片描述
具体用例代码后有
 

2.代码

/* By:咕 问题:TSP问题 算法:蛮力法(顺序查找)分治法(快速排序)贪心法(求上界) 近似算法(贪心+寻找最优贪心值)分支限界法(多城市)动态规划法(少城市) 回溯法(中等规模城市数量)Sherwood概率算法改进版(随机第一个城市)共8种算法 */ #include<iostream> #include<algorithm> #include<cstdio> #include<queue> #include<cmath> #include<ctime> const int INF = ;//自身到自身的一个无穷大数 using namespace std; int n, res = INF, All_upper_bound, first_city = 0,num=0; //城市数量,结果距离,全局上界,第一个城市编号 clock_t start_time;//用于计算时间 int** CITYS;//城市邻接矩阵 int** CITYS_sort;//经过排序后的矩阵 int** dist;//近似算法邻接矩阵 int* city_res;//保存计算好的城市序列 int* city_approximate;//近似算法城市序列 int* city_back_track;//用于回溯法城市序列 int path_length_back_track = 0;//用于回溯法记录路径长度 /* 近似算法所用Prim结构体,前者为生成树,后者为代价序列 */ struct Prim_Tree { 
    int cur_point; int up_point; }; struct Prim_Cost { 
    double cost; int from; }; Prim_Tree *tree; Prim_Cost *lowcost; class City//城市类,对应每个城市 { 
    public: /* 这里用两个动态数组的原因是因为更灵活,更节省空间 */ /* try用来catch住内存超量的错误,catch后直接找近似最优 */ int* city_array;//用于记录已经走过的序列 bool* city_visit;//用于记录每个城市是否到达过 int path_length;//路径长度 int upper_bound;//该城市上界 int lower_bound;//该城市下界 int amount_citys;//已走过的城市数 /* 初始化城市节点 */ City() { 
    try { 
    city_array = new int[n]; city_visit = new bool[n]; path_length = 0; amount_citys = 0; for (int i = 0; i < n; i++) { 
    city_visit[i] = 0; } } catch (exception e) { 
    throw; } } /* 析构函数,释放分配的空间 */ ~City() { 
    delete []city_array; delete []city_visit; } /* 拷贝上一个城市的参数 */ void City_Copy(const City& pre_city) { 
    try { 
    amount_citys = pre_city.amount_citys; for (int i = 0; i < n; i++) { 
    city_visit[i] = pre_city.city_visit[i]; city_array[i] = pre_city.city_array[i]; } path_length = pre_city.path_length; } catch (exception e) { 
    throw; } } /* 加入新的城市节点 */ void Insert_City(int current_city) { 
    try { 
    if (current_city != first_city)//如果加入的不是第一个城市就计算路径 path_length += CITYS[city_array[amount_citys - 1]][current_city]; amount_citys++; city_array[amount_citys - 1] = current_city; city_visit[current_city] = 1; } catch (exception e) { 
    throw; } } /* 上界计算,近似算法(贪心法),用于剪枝和求内存超量的最优近似 */ int Get_Upper_Bound(int cur_city, City& greedy_city_path) { 
    try { 
    if (greedy_city_path.amount_citys == n)//如果访问完所有城市,就返回最后一个城市和第一个城市的闭环长度 return greedy_city_path.path_length + CITYS[cur_city][greedy_city_path.city_array[0]]; int i, min = INF; int next_city = n - 1; /* 贪心法利用最近邻点 ,求当前已经过城市和未经过城市的最小距离,然后将该未经过城市放入已经过城市集合内 重复调用最终求得一个最小生成树,距离就是值+尾和头的距离*/ for (i = 0; i < n; i++) { 
    if (!greedy_city_path.city_visit[i] && min > CITYS[cur_city][i])//如果是未经过城市,且距离已经过城市更近 { 
    min = CITYS[cur_city][i]; next_city = i; } } greedy_city_path.path_length += min; greedy_city_path.city_visit[next_city] = 1; greedy_city_path.city_array[greedy_city_path.amount_citys++] = next_city; return Get_Upper_Bound(next_city, greedy_city_path); } catch (exception e) { 
    throw; } } /* 下界计算,用于剪枝 */ void Get_Lower_Bound() { 
    try { 
    lower_bound = path_length * 2;//已走过路径的两倍 int min = INF, mmin = INF; for (int i = 0; i < n; i++) { 
    if (!city_visit[i]) { 
    /* 首城市和尾城市对应的邻接矩阵行上不在路径上的最小值 */ if (mmin > CITYS[city_array[amount_citys - 1]][i]) mmin = CITYS[city_array[amount_citys - 1]][i]; if (min > CITYS[city_array[0]][i]) min = CITYS[city_array[0]][i]; lower_bound += (CITYS_sort[i][0] + CITYS_sort[i][1]); } } lower_bound += (mmin + min); lower_bound = int(ceil(lower_bound * 1.0 / 2)); } catch (exception e) { 
    throw; } } /* 计算上下界,并且在已有的贪心值中寻找最优,如果有更好的就更新最优近似 */ void Get_Upper_Lower() { 
    try { 
    City* greedy_City = new City(); greedy_City->City_Copy(*this); upper_bound = Get_Upper_Bound(city_array[amount_citys - 1], *greedy_City); if (upper_bound < res)//更新最优近似 { 
    /* 这里更新最优近似的目的是,在内存超量时,可以直接用近似解 并且这种近似解,比直接贪心求到的近似解距离更短(因为经过一部分的分支限界求距离) */ res = upper_bound; for (int i = 0; i < n; i++) { 
    city_res[i] = greedy_City->city_array[i]; } } delete greedy_City; this->Get_Lower_Bound(); } catch (exception e) { 
    throw; } } }; /* 优先序列排序运算符 */ struct cmp { 
    /* 用于优先队列根据下界排序 */ bool operator()(City*& p1, City*& p2)const { 
    return p1->lower_bound > p2->lower_bound; } }; priority_queue<City*, vector<City*>, cmp>City_Array;//基于下界排序的优先队列 static int seed = (int)time(NULL);//基于当前时间的秒的随机数种子 /*Sherwood概率算法(不用于消除时间复杂性与算法联系),用于得到第一个城市节点 虽然原本的Sherwood定义是用于消除时间复杂性与算法联系,但这里我认为当内存不够只能得最优近似时, 多次执行程序,因为无法遍历全部解,所以第一个城市的随机得到的不同结果可以得出最优近似中的最优 (所以命名为Sherwood_Size)*/ int Sherwood_Size(int min, int max)//[min,max]区间内的随机数 { 
    srand(seed); seed = rand(); return (min + rand() % (max - min + 1)); } /* 快速排序(分治法),排序城市邻接矩阵用于下界函数中求行最小的两个元素 */ void Quick_Sort(int arr[], int pos1, int pos2) { 
    if (pos1 < pos2) { 
    int i = pos1, j = pos2, x = arr[pos1]; while (i != j) { 
    while (i != j && arr[j] >= x) j--; if (i < j) arr[i++] = arr[j]; while (i != j && arr[i] < x) i++; if (i < j) arr[j--] = arr[i]; } arr[i] = x; Quick_Sort(arr, pos1, i - 1); Quick_Sort(arr, i + 1, pos2); } } /* 分支限界法,用于城市较多的情况,在内存过高时会使用优化过的贪心法做近似*/ void Branch_And_Bound() { 
    City* Begin_City = new City(); //first_city = Sherwood_Size(0, n - 1);//随机第一个城市节点 /* 这里并未调用是因为算法本身适用于多次执行程序,但是考试系统只能执行一次 */ first_city = 0; Begin_City->Insert_City(first_city); Begin_City->Get_Upper_Lower(); /* 把第一个城市的上界作为全局上界 */ All_upper_bound = Begin_City->upper_bound; City_Array.push(Begin_City); /* 不断从PT表中找节点 */ while (City_Array.size()) { 
    /* 超过55分钟就直接找近似最优 */ if ((clock() - start_time) * 1.0 / CLOCKS_PER_SEC >= 3300) { 
    break; } /*if (City_Array.size()>) { break; }*/ /* try用来catch住内存超量的错误,catch后直接找近似最优 */ try { 
    City* current = City_Array.top();//得到下界小的城市优先搜索 City_Array.pop(); /* 如果距离解只差一个城市,求解 */ if (current->amount_citys == n - 1) { 
    /* 找到剩下的那个城市,计算路径(包含返回开头的路径) */ for (int i = 0; i < n; i++) { 
    if (!current->city_visit[i]) { 
    current->city_array[current->amount_citys++] = i; current->path_length += (CITYS[current->city_array[current->amount_citys - 2]][i] + CITYS[i][current->city_array[0]]); break; } } All_upper_bound = min(All_upper_bound, current->path_length);//更新全局上界函数值 res = min(res, current->path_length);//和已有的解比较谁更优 if (res == current->path_length)//如果这个解更好,那么结果为这个解 { 
    for (int i = 0; i < n; i++) { 
    city_res[i] = current->city_array[i]; } } delete current; continue; } /* 算法核心,寻找下一个城市 */ for (int i = 0; i < n; i++) { 
    if (!current->city_visit[i])//如果没到过这个城市就可以算一算 { 
    City* next_city = new City(); next_city->City_Copy(*current); next_city->Insert_City(i); next_city->Get_Upper_Lower(); if (next_city->lower_bound > All_upper_bound)//该城市下界比全局上界大,丢弃 { 
    delete next_city; continue; } else//加入PT表 { 
    City_Array.push(next_city); } } } delete current; } /* catch到错误后,直接找近似最优 */ catch (exception e) { 
    break; } } } /* 动态规划法,利用二进制优化所以速度快,但正因为用了二进制所以内存占用更高,且没法求最优近似,用于城市较少的情况 */ void Dynamic_Programming() { 
    /* 用二进制表示集合,第x位为1代表x在集合中 例如1表示为001,2表示为010,{1,2}表示为011 */ int i, j, k, temp; int** d = new int* [n];//动态规划路径 int** path = new int* [n];//记录最优路径 int amount_aggregation = (int)pow(2, n - 1); //集合数量,n个城市就有2的n-1次方个集合 for (i = 0; i < n; i++) { 
    d[i] = new int[amount_aggregation]; path[i] = new int[amount_aggregation]; } /* 初始化第0列 */ for (i = 0; i < n; i++) d[i][0] = CITYS[i][0]; /* 算法核心,求最优路径 */ for (i = 1; i < amount_aggregation - 1; i++)//对应一个集合 { 
    for (j = 1; j < n; j++)//对应每个点 { 
    if (((int)pow(2, j - 1) & i) == 0)//如果城市j不在集合中  { 
    d[j][i] = INF; for (k = 1; k < n; k++) { 
    if ((int)pow(2, k - 1) & i)//如果k在集合中 { 
    temp = CITYS[j][k] + d[k][i - (int)pow(2, k - 1)]; //那么就求这个距离 if (temp < d[j][i])//如果更小就更新该城市到集合城市的最优距离 { 
    d[j][i] = temp; path[j][i] = k;//记录最短路径 } } } } } } /* 寻找第一个城市到其余全部城市组成集合的最短路径 */ d[0][amount_aggregation - 1] = INF; for (i = 1; i < n; i++) { 
    temp = CITYS[0][i] + d[i][amount_aggregation - 1 - (int)pow(2, i - 1)]; if (temp < d[0][amount_aggregation - 1]) { 
    d[0][amount_aggregation - 1] = temp;//最短路径长度 path[0][amount_aggregation - 1] = i;//更新第一个城市到哪个城市最短(路径中的第二个城市) } } /* 利用算好的表求最优解 */ res = d[0][amount_aggregation - 1];//最短路径长度 i = 0; j = amount_aggregation - 1; k = 0; city_res[k++] = 0; while (j > 0) { 
    i = path[i][j]; j = j - (int)pow(2, i - 1); city_res[k++] = i; } delete[]d; delete[]path; } int Get_Length(int *arr) { 
    int len=0; for(int i=0;i<n;i++) { 
    if(i!=n-1) len += CITYS[arr[i]][arr[i + 1]]; else len += CITYS[arr[i]][arr[0]]; } return len; } /* 近似算法所用Prim求最小生成树 */ void Approximate_Prim() { 
    int i, j, p = first_city; double minp = INF; tree[0].up_point = -1; tree[0].cur_point = first_city; for (j = 0; j < n; j++) { 
    lowcost[j].cost = dist[p][j]; lowcost[j].from = first_city; } for (i = 1; i < n; i++) { 
    /* 寻找代价序列中最低的节点,加入生成树 */ for (j = 0; j < n; j++) { 
    if (lowcost[j].cost > 0 && lowcost[j].cost < minp&&j!=first_city) { 
    p = j; minp = lowcost[j].cost; } } lowcost[p].cost = 0; minp = INF; tree[i].cur_point = p; tree[i].up_point = lowcost[p].from; /* 因为加入了新结点,所以更新代价序列 */ for (j = 0; j < n; j++) { 
    if (lowcost[j].cost > 0 && lowcost[j].cost > dist[p][j]&&j!=first_city) { 
    lowcost[j].cost = dist[p][j]; lowcost[j].from = p; } } } } /* 近似算法所用,前序遍历最小生成树 */ void Approximate_DFS(int find) { 
    for (int i = 0; i < n; i++) { 
    if (tree[i].up_point == find) { 
    city_approximate[num++] = tree[i].cur_point; Approximate_DFS(tree[i].cur_point); } } } /* 优化后的近似算法,对于多个城市,尝试计算不同起始点的贪心值 */ void Mult_Approximate() { 
    int length_approximate; city_approximate=new int[n]; tree=new Prim_Tree[n]; lowcost=new Prim_Cost[n]; /* 以每个城市作为头结点求最小生成树 */ for (int i = 0; i < n; i++) { 
    first_city = i; num=0; Approximate_Prim(); Approximate_DFS(-1); /* 如果比之前的近似解更好就更新近似解 */ length_approximate=Get_Length(city_approximate); if(length_approximate<res) { 
    res=length_approximate; for(int j=0;j<n;j++) city_res[j]=city_approximate[j]; } } delete []tree; delete []lowcost; delete []city_approximate; } /* 回溯法,用于城市数中等,经测试13-16这个区间是普遍比分支限界法快的 */ void Back_Track_DFS(int depth) { 
    if (depth > n - 1)//如果已经到了底层 { 
    /* 如果该结果比最优好,那么就更新最优 */ if ((CITYS[city_back_track[n - 1]][0] < INF) && (CITYS[city_back_track[n - 1]][0] + path_length_back_track < res)) { 
    res = CITYS[city_back_track[n - 1]][0] + path_length_back_track; for (int i = 0; i < n; i++) { 
    city_res[i] = city_back_track[i]; } } } else { 
    for (int i = depth; i < n; i++) { 
    /* 把序列剩余的点分别尝试交换到这个点后,进行递归求解 */ if ((CITYS[city_back_track[depth - 1]][city_back_track[i]] < INF) && (CITYS[city_back_track[depth - 1]][city_back_track[i]] + path_length_back_track < res)) { 
    swap(city_back_track[depth], city_back_track[i]); path_length_back_track += CITYS[city_back_track[depth - 1]][city_back_track[depth]]; Back_Track_DFS(depth + 1); /* 算完了再换回来 */ path_length_back_track -= CITYS[city_back_track[depth - 1]][city_back_track[depth]]; swap(city_back_track[depth], city_back_track[i]); } } } } void Back_Track() { 
    city_back_track = new int[n]; city_res[0] = 0; for (int i = 0; i < n; i++) { 
    city_back_track[i] = i; } Back_Track_DFS(1); } /* 输入函数,输入城市数量n和城市间的邻接矩阵 */ void Input() { 
    int i, j; cin >> n; city_res = new int[n]; CITYS = new int* [n]; dist = new int* [n]; CITYS_sort = new int* [n]; for (i = 0; i < n; i++) { 
    CITYS[i] = new int[n]; CITYS_sort[i] = new int[n]; dist[i]=new int[n]; } res = INF; for (i = 0; i < n; i++) { 
    for (j = 0; j < n; j++) { 
    cin >> CITYS[i][j]; dist[i][j]=CITYS[i][j]; if (i == j) { 
    CITYS[i][j] = INF; dist[i][j]=0; } CITYS_sort[i][j] = CITYS[i][j]; } Quick_Sort(CITYS_sort[i], 0, n - 1); } } /* 输出函数,可输出结果、路径,以及算法用时 */ void Output() { 
    //cout << "res:" << res << endl << "Path:"; for (int i = 0; i < n; i++) { 
    cout << city_res[i]; if (i != n - 1) cout <<" "; } // cout << endl << "Path length:" << Get_Length(city_res) << endl; // start_time = clock() - start_time; // printf("It took me %d clicks (%f seconds).\n", start_time, ((float)start_time) / CLOCKS_PER_SEC); } /* 随机数测试函数 */ void Test() { 
    int i, j; n = Sherwood_Size(25, 30); city_res = new int[n]; CITYS = new int* [n]; dist = new int* [n]; CITYS_sort = new int* [n]; for (i = 0; i < n; i++) { 
    CITYS[i] = new int[n]; CITYS_sort[i] = new int[n]; dist[i]=new int[n]; } res = INF; for (i = 0; i < n; i++) { 
    for (j = 0; j < n; j++) { 
    CITYS[i][j] = Sherwood_Size(1, 1000); dist[i][j] = CITYS[i][j]; if (i == j) { 
    CITYS[i][j] = INF; dist[i][j]=0; } CITYS_sort[i][j] = CITYS[i][j]; } Quick_Sort(CITYS_sort[i], 0, n - 1); } cout << n << endl; /*for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { cout << CITYS[i][j] << " "; } cout << endl; }*/ } int main() { 
    Input(); //Test(); start_time = clock(); if (n <= 12) Dynamic_Programming();//少于13个城市调用动态规划法 else if (n > 12 && n <= 15) Back_Track(); else { 
    Mult_Approximate();//不同起点的Prim求近似 Branch_And_Bound();//13个或多于13个调用分支限界法 } Output(); return 0; } /* 更多测试用例(比较大的,写不进去,后续可以用文件上传) 5 3 1 5 8 3 6 7 9 1 6 4 2 5 7 4 3 8 9 2 3 16 */ 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/145271.html

(0)
上一篇 2025-04-20 20:45
下一篇 2025-04-20 21:00

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信