C++ 动态规划求解TSP(旅行商问题)

动态规划“四部曲”

  1. 问题结构分析: 给出问题表示,明确原始问题。

  2. 递推关系建立: 分析最优(子)结构,构造递推公式。

  3. 确定计算顺序: 确定计算顺序,依次求解问题。

  4. 最优方案追踪: 记录决策过程,输出最优方案。

TSP问题介绍

旅行商问题,即TSP问题(Traveling Salesman Problem)是数学领域中著名问题之一。假设有一个人要拜访n个城市,从当前所在城市出发,经过且仅经过一次所有城市,回到出发的城市。选择一条路径使得其路程为所有路径中的最小值。
TSP问题模型

使用动态规划分析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,Vk]}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=2D[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的增大呈指数爆炸增长。

  • 12
    点赞
  • 112
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
TSP问题(Traveling Salesman Problem,旅行商问题)是一个著名的NP完全问题,它的目标是寻找一条路径,使得一个旅行商可以经过所有城市恰好一次,最终回到起点,并且路径长度最小。在本文中,我们将介绍如何使用动态规划算法求解TSP问题。 动态规划的基本思想是将问题分解成子问题,并将子问题的解存储起来以便重复利用。对于TSP问题,我们可以采用以下步骤来构建动态规划算法: 1.定义状态:我们需要定义一个状态,它表示从起点出发到当前城市经过的所有城市的集合。例如,如果我们从城市A出发,到达了城市B和城市C,那么状态可以表示为{A,B,C}。 2.状态转移方程:假设我们当前的状态是S,下一个要到达的城市是i,则我们可以通过以下公式计算从S到达{i}的最短距离: dp[S][i] = min(dp[S-{i}][j] + dis[j][i]),其中j∈S-{i} 这个公式的意思是,在状态S的基础上,我们要从中删除i,然后枚举剩余的城市j,计算从S到达{j}的最短距离加上从{j}到{i}的距离,取所有可能的结果中的最小值。 3.边界条件:我们需要指定起点的状态和到达每个城市的距离。例如,如果我们从城市A开始,那么我们的起始状态就是{A},到达每个城市的距离可以通过计算两个城市之间的距离得到。 4.最终答案:我们需要遍历所有可能的状态S,找到从起点到达所有城市恰好一次并回到起点的最短路径长度。 下面是C++代码实现: ```c++ #include <iostream> #include <vector> #include <algorithm> using namespace std; const int INF = 0x3f3f3f3f; int tsp(int n, vector<vector<int>>& dis) { vector<vector<int>> dp(1 << n, vector<int>(n, INF)); // 初始状态 dp[1][0] = 0; // 动态规划 for (int s = 1; s < (1 << n); s++) { for (int i = 0; i < n; i++) { if (s & (1 << i)) { for (int j = 0; j < n; j++) { if (s != (1 << j) && (s & (1 << j))) { dp[s][i] = min(dp[s][i], dp[s - (1 << i)][j] + dis[j][i]); } } } } } // 找到最小值 int ans = INF; for (int i = 0; i < n; i++) { ans = min(ans, dp[(1 << n) - 1][i] + dis[i][0]); } return ans; } int main() { int n, m; cin >> n >> m; vector<vector<int>> dis(n, vector<int>(n, INF)); // 输入距离矩阵 for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; dis[u][v] = w; } // 计算最短路径长度 int ans = tsp(n, dis); cout << ans << endl; return 0; } ``` 时间复杂度分析:该算法的时间复杂度为O(n^2 * 2^n),其中n是城市的数量。这是因为动态规划需要计算所有可能的状态,而每个状态都需要枚举所有城市,所以总的计算量是O(n * 2^n),每次计算需要O(n)的时间复杂度,因此总的时间复杂度为O(n^2 * 2^n)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值