TSP问题-多种算法求解
目录
- 前言
- 问题及思路
- 1.问题概述
- 2.设计思路
- 源码及测试
- 1.输入
- 2.代码
前言
算法大作业,综合应用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 = 100000;//自身到自身的一个无穷大数
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()>4000000){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]];elselen += 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
100000 3 1 5 8
3 100000 6 7 9
1 6 100000 4 2
5 7 4 100000 3
8 9 2 3 100000
16
*/
TSP问题-多种算法求解相关推荐
- 【WPA TSP】狼群算法求解旅行商问题【含Matlab源码 211期】
⛄一.TSP简介 旅行商问题(traveling salesman problem,TSP)是一种常见的路径优化问题,其目的是为了求得一条经过所有城市的最短路径.现实生活中,很多问题都被抽象为TSP进 ...
- 0-1背包问题的多种算法求解(C语言)
1.问题描述 有一个容量为V的背包,还有n个物体.现在忽略物体实际几何形状,我们认为只要背包的剩余容量大于等于物体体积,那就可以装进背包里.每个物体都有两个属性,即体积w和价值v.使物 ...
- 【路径规划-TSP问题】基于粒子群结合蚁群算法求解旅行商问题附matlab代码
1 内容介绍 一种基于粒子群优化的蚁群算法求解TSP问题的方法.该方法在求解TSP问题时,利用粒子群优化的思想,对蚁群算法的参数取值进行优化并选择.在粒子群算法中,将蚁群算法的5个参数(q,α,β,ρ ...
- 粒子群算法java_基于粒子群算法求解求解TSP问题(JAVA)
一.TSP问题 TSP问题(Travelling Salesman Problem)即旅行商问题,又译为旅行推销员问题.货郎担问题,是数学领域中著名问题之一.假设有一个旅行商人要拜访n个城市,他必须选 ...
- 贪心算法求解 TSP 旅行商问题及其实现
文章目录 一.TSP 概述 1. TSP 2. 数学模型 3. TSP分类 二.贪心算法 1. 算法思路 2. 算法框架 3. 问题 三.贪心算法求解 TSP 一.TSP 概述 1. TSP 旅行商问 ...
- 【路径规划】基于灰狼算法求解旅行商TSP问题matlab源码
一.旅行商问题 TSP问题即旅行商问题,经典的TSP可以描述为:一个商品推销员要去若干个城市推销商品,该推销员从一个城市出发,需要经过所有城市后,回到出发地.应如何选择行进路线,以使总的行程最短.从图 ...
- 利用HTML5 Canvas和Javascript实现的蚁群算法求解TSP问题演示
HTML5提供了Canvas对象,为画图应用提供了便利. Javascript可执行于浏览器中, 而不须要安装特定的编译器: 基于HTML5和Javascript语言, 可随时编写应用, 为算法測试带 ...
- MATLAB实战系列(二十九)-头脑风暴优化(BSO)算法求解旅行商问题(TSP)-交叉算子
前言 代码明细可参见 MATLAB实战系列(八)-头脑风暴优化(BSO)算法求解旅行商问题(TSP)(附MATLAB代码) 交叉算子的实现机制 我们还是以求解TSP问题为例,8个城市的坐标如下所示. ...
- matlab实战系列之人工鱼群算法求解TSP问题原理解析(下篇源码解析)
从算法的名字中可以看出该算法是群体智能优化算法中的一种,人工鱼群算法通过模拟鱼群的觅食.聚群.追尾.随机等行为在搜索域中进行寻优. 人工鱼群算法有三个比较重要的概念:视野范围.k-距离邻域.多条鱼的中 ...
最新文章
- java 过滤器 中文_Java web整站中文过滤器实现
- 英特尔分拆McAfee:31亿美元将多数股权卖给投资公司TPG
- vue+element-ui实现分页
- 关于Mybatis的各种配置文件
- router-link标签学习
- flatform installer web 安装php_Windows server 2019 安装 IIS PHP 环境无标题笔记
- js 通用 1000 金额 三位格式化 1,000
- mysql5.7zib配置_mysql-5.7.13 解压版安装教程
- 告诉刚入行的兄弟们,钱是这么赚的!
- bash shell for循环1到100
- 使用SESSION实现PHP会话的步骤
- C++转Java快速入门
- oppok3如何刷机_oppok3刷机方法
- mac下搭建stm32开发环境
- 拼多多店铺的先用后付|盛天海科技
- 深度学习、机器学习、人工智能的区别
- 制造业增值税从16%下降到13%,我们是否应该降价出售?
- Spring框架中的单例Beans是线程安全的么
- Linux的常用命令有哪些?
- accept4: too many open files; retrying in 1s