一、问题描述
有一个旅行商由某城市出发,经过所有给定的 n n n 个城市后,再回到出发的城市。除了出发的城市外,其它城市只经过一回。这样的回路可能有多个,求其中路径成本最小的回路。
二、穷举法解决
2.1 介绍
穷举法的本质是全排列。如下图对于四个点都连通的图,我们假定从 a a a 点出发,可以将获得 ( 4 − 1 ) ! (4-1)! (4−1)! 条路径。(公式为 ( n − 1 ) ! (n-1)! (n−1)!)
2.2 代码
#include <iostream>
using namespace std;
//我们这里题目规模比较小
int x[10]={0}; //城市编号数组,初值赋为0
int bestx[10]={0}; //路线,初值赋为0
int w = 0; //过渡变量
int bestw = 1000; //最优的费用
void Tsp(int a[10][10], int n, int s)
{
if (s == n)
{
w = 0;//清零
cout << "bestx: ";
for (int i = 0; i < n; i++) {
bestx[i] = x[i];
cout << bestx[i] << " ";
if (i < n - 1) {
w += a[x[i]][x[i + 1]];
}
else {
w += a[x[i]][x[0]];//回到起点的费用
}
}
cout << "w:" << w << endl;
if (bestw > w)
{
bestw = w;//更新最优值
}
}
else
{
for (int i = s; i < n; i++)
{
//为什么要交换呢?因为要将x[s]作为起点进行往下搜索
int t = x[i]; x[i] = x[s]; x[s] = t;
Tsp(a, n, s + 1);
t = x[i];
x[i] = x[s];
x[s] = t;
}
}
}
int main()
{
cout<<"请输入城市的个数: "<<endl;
int n; //城市个数
cin>>n;
for (int i = 0; i < n; i++)
{
x[i] = i; //表示第i个城市编号,0到n-1
}
int a[10][10]; //a[i][j]表示从第i个城市到第j个的费用
cout<<"请输入权值矩阵: "<<endl;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
cin >> a[i][j];
}
}
Tsp(a, n, 1);//传1表示只搜从0开始的全排,传0表示所有全排
cout << "最优的是:" << bestw << endl;
return 0;
}
三、回溯法解决
#include<iostream>
#include<algorithm>
#define MAX 10
using namespace std;
int n; //城市个数
int a[MAX][MAX]; //城市间距离
int x[MAX]; //记录路径
int bestx[MAX] = {0}; //记录最优路径
int bestp = 63355; //最短路径长
int cp = 0; //当前路径长
void backpack(int t){
if(t>n)
{
if((a[x[n]][1])&&(a[x[n]][1]+cp<bestp))
{
bestp = a[x[n]][1]+cp;
for(int i = 1;i<=n;i++)
{
bestx[i] = x[i];
}
}
}
else
{
for(int i = t;i<=n;i++){
//约束为当前节点到下一节点的长度不为0,限界为走过的长度+当前要走的长度之和小于最优长度
if((a[x[t-1]][x[i]])&&(cp+a[x[t-1]][x[i]]<bestp)){
swap(x[t],x[i]);
cp+=a[x[t-1]][x[t]];
backpack(t+1);
cp-=a[x[t-1]][x[t]];
swap(x[t],x[i]);
}
}
}
}
int main(){
cout<<"请输入城市的个数:"<<endl;
cin>>n; //顶点数
for(int i = 1;i<=n;i++){
x[i] = i; //表示第i个城市编号
}
cout<<"请输入权值矩阵:"<<endl;
for(int i = 1;i<=n;i++){
for(int j = 1;j<=n;j++){
cin>>a[i][j];
}
}
backpack(2);
cout<<"最少旅行费用为:"<<bestp<<endl;
cout<<"旅行路径为:"<<endl;
for(int i = 1;i<=n;i++){
cout<<bestx[i]<<" ";
}
cout<<bestx[1];
return 0;
}
四、分支界限法
4.1 介绍
分支限界法就是先将根结点放入活结点表中,然后循环取出表头结点,如果满足约束条件和限界条件就可以将当前结点的子结点(或者说下一级结点)放入活结点表中,直至得到所求解或者是活结点表为空为止。根据活结点表的存储方式可以将分支限界法分为队列式分支限界法和优先队列式分支限界法。这两种方法使用到的数据结构来是队列和堆,如有需要可以自己写,当然用直接用C++标准模板库中的queue和priority_queue会方便很多。
4.2 代码
#include <bits/stdc++.h>
#include <string>
#include <vector>
using namespace std;
#define MAXN 10
int e[MAXN][MAXN];
int n, m, ans = 0x3f3f3f3f, vis[MAXN];
string anspath;
void bfs(){
queue<pair<string, int> > q;
int dis = 0;
vector<int> path;
q.push({"1", 0});
while(!q.empty()){
string path = q.front().first;
int dis = q.front().second;
int pos = path[path.length()-1] - '0';
if( path.length() == n ){
dis += e[pos][1];
if(dis < ans) ans = dis, anspath = path + char(1 + '0');
}
else{
for(int i = 1; i <= n; i ++ ){
if(find(path.begin(), path.end(), (i + '0')) != path.end()) continue;
if(dis + e[pos][i] > ans) continue;
q.push({path + char(i + '0'), dis + e[pos][i]});
}
}
q.pop();
}
}
void show(){
cout << ans << endl;
for( int i = 0 ; i <= n ; i ++ ) cout << anspath[i] << (i == n ? "\n" : " ");
}
int main(){
cin >> n >> m;
for( int i = 0 ; i < m ; i ++ ){
int u, v, w;
cin >> u >> v >> w;
e[u][v] = e[v][u] = w;
}
bfs();
cout << ans << endl;
for( int i = 0 ; i <= n ; i ++ ) cout << anspath[i] << (i == n ? "\n" : " ");
}