最短Hamilton路径

题目描述

给定一张 n(n≤20) 个点的带权无向图,点从0∼n−1标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。

输入描述

第一行一个整数n。
接下来n行每行n个整数,其中第i行第j个整数表示点i到j的距离(一个不超过10710^7的正整数,记为a[i,j])。
对于任意的 x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且a[x,y]+a[y,z]≥a[x,z]。

输出描述

输出一个整数,表示最短 Hamilton 路径的长度。

输入样例

4
0 2 1 3
2 0 2 1
1 2 0 1
3 1 1 0

输出样例

4

说明

从0到3的Hamilton路径有两条,0-1-2-3和0-2-1-3。前者的长度为2+2+1=5,后者的长度为1+2+1=4

思路

首先题目需要求出最短路径,按照朴素的方法,需要遍历所有的转态,即20个点的所有排列,状态数量为 20!,显然这是无法接受的时间复杂度,所以我们需要另一种策略,即将每次产生的最优解状态保存,使用状态转移,

状态转移方程:step[status][j] = min{step[status - (1 << j)][k] + weight[k][j]}

status是当前已经过的点的集合,若其第i位为1,则表示第i个点已被遍历,否则则未经过,status - (1 << j)表示从经过的所有点中去除点j, weight[k][j]表示从点k到点j的路径长度

所以这个状态转移方程就是找一个中间点 k,将已经走过点的集合 status 中去除掉 j ,然后再加上从 k 到 j 的权值

现在我们有了基本思路,可以开始设计代码了,第一时间,我想到的是记忆搜索,代码写好后发现了一个严重的问题,栈溢出了,由于递归过程中产生了大量的自动变量,栈区大小熬不过堆区的几十M的steps[1 << N][N],所以又改成了使用动态规划,终于把题目给AC了。回顾一下思路历程 暴力搜索 --> 记忆搜索 --> 动态规划, 当时最先想到的怎么会是直接暴搜呢,应该还是还是做(tai)得(cai)少(le)。

下面给出记忆搜索(不能通过)和动态规划的代码。

示例代码

#include <iostream>
#include <limits>
using namespace std;

const int INF = std::numeric_limits<int>::max() / 2;
const int N = 21;

int weight[N][N];
int steps[1 << N][N];
int n;

#define DP

#ifdef DP // 使用DP算法

int get_step(int ss, int endpoint)
{
    steps[1][0] = 0;
    for (int status = 1; status < 1 << n; status++)
    {
        for (int endpoint = 0; endpoint < n; endpoint++) if (status & (1 << endpoint))
        {
            for (int j = 0; j < n; j++)  if (status & (1 << j))
                steps[status][endpoint] = min(
                    steps[status][endpoint],
                    steps[status - (1 << endpoint)][j] + weight[j][endpoint]);
        }
    }
    return steps[ss][endpoint];
}

#else // 使用记忆搜索算法

int get_step(int status, int endpoint)
{
    if (steps[status][endpoint] != INF)
    {
        return steps[status][endpoint];
    }
    if (status == 1 && endpoint == 0)
    {
        return steps[status][endpoint] = 0;
    }

    for (int k = 0; k < n; k++)
    {
        if (((status - (1 << endpoint)) >> k) & 1)
        {
            steps[status][endpoint] = min(
                steps[status][endpoint],
                get_step(status - (1 << endpoint), k) + weight[k][endpoint]);
        }
    }
    return steps[status][endpoint];
}

#endif

int main(int argc, char const* argv[])
{
    for (auto& ints : steps)
        for (auto& i : ints)
            i = INF;

    std::cin >> n;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n;j++)
            std::cin >> weight[i][j];

    std::cout << get_step((1 << n) - 1, n - 1);

    return 0;
}