旅行商问题(《挑战程序设计竞赛》)的理解

TSP问题(Traveling Salesman Problem,旅行商问题),由威廉哈密顿爵士和英国数学家克克曼T.P.Kirkman于19世纪初提出。问题描述如下:有若干个城市,任何两个城市之间的距离都是确定的,现要求一旅行商从某城市出发必须经过每一个城市且只在一个城市逗留一次,最后再回到原点的最小路径成本。

下面是《挑战程序设计竞赛》中关于该问题的的分析加上本人的理解笔记:

所有可能的路线共有(n-1)!种。这是一个非常大的值,即使n(假如为16)已经很小了,仍然无法试遍每一种情况。对于这个问题,我们使用DP来解决。首先让我们试着写出它的递推式。

假设现在已经访问过的顶点的集合(起点0当做还未访问过的顶点)为S,当前所在的顶点为v,用dp[S][v]表示从v出发访问剩余的所有顶点,最终回到顶点0的路径的权重总和的最小值。由于从v出发移动到任意的一个节点u不属于S,因此有如下递推式

dp[V][0] = 0   //这是初始条件,即作为递推过程的已知条件。

dp[S][v] = min{dp[S U {u}] + dis(v,u) | u 不属于S}

我们只要按照这个递推式进行计算就可以了。由于在这个递推式中,有一个下标是集合而不是普通的整数,因此需要稍加处理。首先我们试着使用记忆化搜索求解。虽然有一个下标不是整数,但是我们可以把它编码成一个整数,或者给它们定义一个全序关系并用二叉搜索树存储,从而可以使用记忆化搜索来求解。特别的,对于集合我们可以把每一个元素的选取与否对应到一个二进制位里,从而把状态压缩成一个整数,大大方便了计算和维护。对于不是整数的情况,很多时候很难确定一个合适的递推式,因此使用记忆化搜索可以避免这个问题。不过在这个问题中,对于任意两个整数i和j,如果它们对应的集合S(i)属于S(j),就有i<=j,因此可以像下面的写法一样,通过循环求出答案。

对数组dp结果逆推,初始条件:dp[(1<<n)-1][0] = 0表示所有顶点都走过,并且当前已处在原点的dp值,手推n=3或n=4时的递推,非常有助于理解,然后就会发现这个逆推过程的结果是可算的。

二进制位表示顶点的访问已否。如n=3时,s<1<<n有如下结果:

000,001,010,011,100,101,110,111,有二进制有三位(n=3),每一位上的1表示对应顶点已访问过,如s=100,表示访问过顶点3;

int dp[1<<N][N];//记忆化搜索使用的数组
void solve(){
    //用足够大的值初始化数组
    memset(dp,INF,sizeof(dp));
    dp[(1<<n)-1][0] = 0;
    //根据递推式依次计算
    for(int s = (1<<n)-2; s >= 0; s--){    
        for(int v = 0; v < n; v++){

if(s>>v&1){   //判断v是否在已走过的顶点中,如果是,则该点做为当前点遍历
            for(int u = 0; u < n; u++){    从v出发访问没有走过的点
                if(!(s>>u & 1)){    
                    dp[s][v] = min(dp[s][v],dp[s|1<<u][u]+dis[v][u]);//更新从节点v出发访问不同未访问点的的最小距离
                }
            }

        }
        }
    }
    printf("%d\n",dp[0][0]);
}

猜你喜欢

转载自blog.csdn.net/clz16251102113/article/details/81150146