动态规划-TSP问题

一、问题引入

  TSP问题(旅行商问题)是指旅行家要旅行n个城市,要求各个城市经历且仅经历一次然后回到出发城市,并要求所走的路程最短。
  假设现在有四个城市,0,1,2,3,他们之间的代价如图一,可以存成二维表的形式


图1

  现在要从城市0出发,最后又回到0,期间1,2,3都必须并且只能经过一次,使代价最小。

二、求解思路

1、动态规划思路

  • 最优子结构(问题的最优解能够通过其子问题的最优解得到)
  • 重叠子问题(问题有重叠,用备忘录记录减少时间复杂度)
    从下往上构造备忘录(这里为一个压缩二维矩阵)


    image.png

2、数据结构

  1. 备忘录(压缩二维矩阵):d[MAX][1 << (MAX-1)]
    求法:即利用上面的递归公式从下到上求解,具体见代码解释。
  2. 记录最优路线点矩阵(压缩二维矩阵):route[MAX][1 << (MAX-1)]
    求法:就是当利用递归公式求解出子问题最优值后,给备忘录赋值的同时,也给最优路线点矩阵赋选到的最优分割点的点序号。
  3. 其他结构:
    dis[MAX][MAX]:距离地图表
    p[MAX]:记录最优路线

3、求解中注意的地方

  1. 压缩矩阵的表示
    例如4个城市{0,1,2,3},{1,2,3}依次为{},{1},{2},{1,2},{3},{1,3},{2,3},{1,2,3},如果用111表示城市321,则上述子集转换成二进制为0,01,10,11,100,101,110,111,十进制恰好是0,1,2,3,4,5,6,7。虽然子集{1,2}在{3}之前,但遍历子集{1,2}的过程并不会使用过程矩阵中关于3的行列,因此不需要排序。
  2. 判断一个城市是否位于子集中
    (1 << (i - 1))&j):判断城市i是否在子集j中,为1则在,为0不在。
  3. 位运算符优先级小,记得加括号。
  4. 要想求得问题的最小代价,得在备忘录建立好后,再执行一次。

4、代码实现

#include <iostream>
#include <stdio.h>
#include<iomanip>
#define MAX 12
using namespace std;
 
int dis[MAX][MAX], d[MAX][1 << (MAX-1)];//距离地图表,d函数表,d[]为动态规划存储的城市经过矩阵
int route[MAX][1 << (MAX-1)];//记录最优路线点 
int p[MAX];//记录最优路线 
 
int main()
{
    int n, temp,minDis,minPoint,count;
    ////给距离地图表赋值
    cout<<"请输入城市个数:" ;
    cin >> n;
    cout<<"输入各城市之间的路程费用:"<<endl ;
    for (int i = 0; i < n; i++){
        for (int j = 0; j < n; j++){
            scanf("%d", &dis[i][j]);
        }
    }
 
    //给d函数表赋值
    for (int i = 1; i<n; i++){     //将所有城市到第0个城市的路径初始化为两市间的距离
        d[i][0] = dis[i][0];
        route[i][0] = 0;
    }
    //给除了第0行以外的其余部分赋值 
    for (int j = 1; j<(1 <<(n-1)); j++){//j用二进制表示的城市集合,先说j再说i,动态规划从j的底层到上层,j数值越来越大
        for (int i = 1; i<n; i++){    
            //i不在j表示的城市集合中,可以对d[i][j]赋值,否则赋0;
            if (((1 << (i - 1))&j) == 0){         
                //temp=dis[i][k]+d[k][V-{k}]开始对V中所有的k进行遍历
                int minDis = 60000;
                for (int k = 1; k<n; k++){
                    if ((1 << (k - 1))&j)  {//k表示的城市在j表示的城市集合中 
                        temp = dis[i][k] + d[k][j - (1 << (k - 1))];
                        if (temp<minDis){//d(1,{2,3})=min{ C12+d(2,{3})||C13+d(3,{2})}
                            minDis = temp;   //所有k中最小的距离
                            minPoint=k; 
                        }
                    }
                }
                d[i][j] = minDis;//给d函数表每个位置赋值
//              cout<< d[i][j]<<" ";
                route[i][j]=minPoint;//从城市集j中选出minPoint点递归为最优 
            }
            
        }
    }
    
    //对第0行最后一个d(0,{123})赋值,d(0,{1,2,3})=min {C01+d(1,{2,3})|| C02+d{2,{1,3}}||C03+d{3,{1,2}})
    minDis = 600;
    for (int k = 1; k<n; k++){
        temp = dis[0][k] + d[k][((1 << (n - 1)) - 1) - (1 << (k - 1))];//d[k][{123}-{k}]
        if (minDis>temp){
            minDis = temp;
            minPoint=k; 
        }
    }
    d[0][(1 << (n - 1)) - 1] = minDis;//数组从零开始,所以-1 
    route[0][(1 << (n - 1)) - 1] = minPoint;
    
    //此部分可以输出看看形成的d[][]矩阵,便于理解执行过程
    for(int i=0;i<n;i++){    
        for(int j=0;j<(1<<(n-1));j++){
            printf("%3d",d[i][j]);
        }
        cout<<endl;
    } 
 
    cout <<"最小代价:"<< d[0][(1 << (n - 1)) - 1]<<endl;
    
    //求出最优路径p[12]
    count=0;p[count++]=0;
    int flat=(1 << (n - 1)) - 1;
    do
    {
        temp=route[p[count-1]][flat];
        p[count++]=temp;
        flat-=(1<<(temp-1));
    } 
    while(flat>0);//所有点都被查找过一遍,最后一次是空集0要先执行一遍(所以用do-while,记得加分号) 
    
    //打印最优路径
    cout<<"最优路线:" ;
    cout<<p[0];
    for(int i=1;i<=n;i++)
    {
        cout<<"->"<<p[i];   
    } 
    
    return 0;
}

5、运行结果

image.png

6、计算过程

从0城市出发经城市1、2、3然后回到0城市的最短路径长度是:
d(0,{1, 2, 3})=min{c01+d(1, { 2, 3}), c02+d(2, {1, 3}), c03+d(3, {1, 2})}
这是最后一个阶段的决策,而:
d(1, {2, 3})=min{c12+d(2, {3}), c13+ d(3, {2})}
d(2, {1, 3})=min{c21+d(1, {3}), c23+ d(3, {1})}
d(3, {1, 2})=min{c31+d(1, {2}), c32+ d(2, {1})}
这一阶段的决策又依赖于下面的计算结果:
d(1, {2})= c12+d(2, {}) d(2, {3})=c23+d(3, {})
d(3, {2})= c32+d(2, {}) d(1, {3})= c13+d(3, {})
d(2, {1})=c21+d(1, {}) d(3, {1})=c31+d(1, {})
而下式可以直接获得(括号中是该决策引起的状态转移):
d(1, {})=c10=5(1→0) d(2, {})=c20=6(2→0) d(3, {})=c30=3(3→0)

再向前倒推,有:
d(1, {2})= c12+d(2, {})=2+6=8(1→2) d(1, {3})= c13+d(3, {})=3+3=6(1→3)
d(2, {3})= c23+d(3, {})=2+3=5(2→3) d(2, {1})= c21+d(1, {})=4+5=9(2→1)
d(3, {1})= c31+d(1, {})=7+5=12(3→1) d(3, {2})= c32+d(2, {})=5+6=11(3→2)
再向前倒退,有:
d(1, {2, 3})= min{c12+d(2, {3}), c13+ d(3, {2})}=min{2+5, 3+11}=7(1→2)
d(2, {1, 3})=min{c21+d(1, {3}), c23+ d(3, {1})}=min{4+6, 2+12}=10(2→1)
d(3, {1, 2})=min{c31+d(1, {2}), c32+ d(2, {1})}=min{7+8, 5+9}=14(3→2)
最后有:
d(0, {1, 2, 3})=min{c01+ d(1, { 2, 3}), c02+ d(2, {1, 3}), c03+ d(3, {1, 2})}
=min{3+7, 6+10, 7+14}=10(0→1)
所以,从顶点0出发的TSP问题的最短路径长度为10,路径是0→1→2→3→0。

参考文献

TSP问题计算过程
实现思路
TSP的动态规划解法-详细解析
二维表构建
c++位运算符介绍及举例

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容