C++ 动态规划求解TSP(旅行商问题)
动态规划“四部曲”
-
问题结构分析: 给出问题表示,明确原始问题。
-
递推关系建立: 分析最优(子)结构,构造递推公式。
-
确定计算顺序: 确定计算顺序,依次求解问题。
-
最优方案追踪: 记录决策过程,输出最优方案。
TSP问题介绍
旅行商问题,即TSP问题(Traveling Salesman Problem)是数学领域中著名问题之一。假设有一个人要拜访n个城市,从当前所在城市出发,经过且仅经过一次所有城市,回到出发的城市。选择一条路径使得其路程为所有路径中的最小值。
使用动态规划分析TSP
问题结构分析
给出问题表示
D[location , V]:从当前所在城市location经过V‘集合(剩余未访问城市的集合)的最短路径。
明确原始问题
D[i , V]:从当前所在城市location经过V‘集合(剩余未访问城市的集合)的最短路径。
递推关系建立
分析最优(子)结构
D[i , V] = Cik + D[k , V - {k} ]
构造递推公式
当V‘为空集时,此时所有顶点均被访问过,直接回到起始顶点即可。(a代表起始点)
{ C i a , V = { } 且 i ≠ a m i n { C i k + D [ k , V − k ] } , V ≠ { } \left\{ \begin{array}{c} Cia~ , V=\{\}且i≠a \\ min \{{ Cik + D[k , V - {k} ] \}} ,V≠\{\} \end{array}\right. {Cia ,V={}且i=amin{Cik+D[k,V−k]},V={}
确定计算顺序
✅:可以直接求得。
❌:不存在该路径。
D[2,{1,3}] = min{ C21+D[1,{}] , C23 + D[3,{}] }
{} | {1} | {2} | {1,2} | {3} | {1,3} | {2,3} | {1,2,3} | |
---|---|---|---|---|---|---|---|---|
i=1 | ✅ | ❌ | ❌ | ❌ | ❌ | |||
i=2 | ✅ | ❌ | ❌ | D[2,{1,3}] | ❌ | ❌ | ||
i=3 | ✅ | ❌ | ❌ | ❌ | ❌ |
由表格发现,D[2,{1,3}]依赖其左边部分的子问题,确定计算顺序由左到右。从不同顶点出发的最优解在表中的位置不同。
最优方案追踪
创建一个Rec数组用于存放决策,记录前驱节点。
{} | {1} | {2} | {1,2} | {3} | {1,3} | {2,3} | {1,2,3} | |
---|---|---|---|---|---|---|---|---|
i=1 | ❌ | ❌ | ❌ | ❌ | ||||
i=2 | ❌ | ❌ | ❌ | ❌ | ||||
i=3 | ❌ | ❌ | ❌ | ❌ |
C++代码
创建二维数组求解、追踪最优解,对于n顶点的图,需要创建一个n*2n的二维数组,对于前一部分中列的排序为什么是{}、{1}、{2}、{1,2}、{3}、{1,3}、{2,3}、{1,2,3},大家可能存在疑问,实际上这是为了方便编写代码,使用数字的二进制表示集合中存在的元素,例如000(第0列)表示空集,111(第7列)表示{1,2,3}。
#include<iostream>
#include<cmath>
using namespace std;
#define N 9999
//TSP问题求解函数
void TSP(int n,int** graph,int location){
//构建二维数组[i,2^n],j模拟集合个数
int **D = new int*[n+1]; //建行0~n,为方便理解和表示,第0行未使用
int **Rec = new int*[n+1]; //建立Rec
for(int i=1;i<=n;i++){
//建列表示集合
D[i] = new int [(int)pow(2,n)];
Rec[i] = new int [(int)pow(2,n)];
}
//初始化D、Rec
for(int i=1;i<=n;i++){
D[i][0]=graph[i][location]; //D[i,{}]
Rec[i][0]= -1;
for(int j=1;j<=(int)pow(2,n)-1;j++){
D[i][j]=N;
Rec[i][j] = -1;
}
}
//动态规划
for(int j=1;j<=(int)pow(2,n)-1;j++){ //对每一列求解
for(int i=1;i<=n;i++){ //每一行找最短路径
int min = N;
if(((int)pow(2,i-1) & j) == 0){ //顶点集不能包含i
int length = N;
for(int k=1;k<=n;k++){
if((int)pow(2,k-1) & j ){ //若顶点在集合中
length = graph[i][k] + D[k][j-(int)pow(2,k-1)];
if(length < min){
min = length;
D[i][j] = min;
Rec[i][j] = k;//局部最优决策
}
}
}
}
}
}
cout<<"最短长度:"<<D[location][(int)pow(2,n)-1-(int)pow(2,location-1)]<<endl;//最短路径长度
cout<<"最短路径为:"<<location;
int row = location;
int column = (int)pow(2,n)-1-(int)pow(2,row-1);
while(column > 0){
cout<< "->"<<Rec[row][column];
row = Rec[row][column];
column -= (int)pow(2,row-1);
}
cout<<"->"<<location<<endl;
}
int main(){
cout<<"旅行家需要游历多少个城市?:"<<endl;
int n;
cin>>n;
//建立二维数组模拟邻接矩阵
int **graph=new int* [n+1];
for(int i=1;i<=n;i++)
graph[i] = new int[n+1];
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cout<<"输入邻接矩阵graph["<<i<<"]["<<j<<"]的权:"<<endl;
cin>>graph[i][j];
}
}
cout<<"旅行家现在在第几个城市?"<<endl;
int location;
cin>>location;
TSP(n,graph,location); //TSP求解
return 0;
}
时间复杂度分析
如代码所示,求解TSP问题时间花费最主要在n*2n的二维数组的操作上,故时间复杂度为O(n*2n),时间复杂度随n的增大呈指数爆炸增长。