📜  哈密顿路径(使用动态规划)(1)

📅  最后修改于: 2023-12-03 15:37:12.255000             🧑  作者: Mango

哈密顿路径(使用动态规划)

哈密顿路径指的是在无向图中找到一条路径,使得该路径经过每个顶点恰好一次。这个问题是一个NP完全问题,没有已知的多项式时间算法。

然而,在特定的情况下,我们可以通过动态规划来解决这个问题,使得复杂度降到了多项式级别。具体来说,在一个DAG(有向无环图)中,我们可以通过填写一个二维数组来计算出任意两个点之间的最短路径。然后,我们可以通过递归来求出哈密顿路径。

动态规划的解法

我们先定义一个$dp_{i,S}$表示经过$S$集合中的所有点,以$i$为最后一个点的所有哈密顿路径的长度。$S$可以是任意子集,但是必须包含$i$。如果$S$只包含$i$,那么$dp_{i,S}$就是$0$,因为最后一个点和起点是同一个点。

那么,如何计算$dp_{i,S}$呢?我们可以考虑从$S\setminus{i}$中的某个点$j$转换至$i$,从而得到一条新的哈密顿路径。这个$j$在新路径中是倒数第二个点。新路径的长度为$dp_{j,S\setminus{i}}$加上从$j$到$i$的边的长度。我们只需要枚举$j$,然后找到其中长度最小的路径,即可获得$dp_{i,S}$。

下面给出详细的算法流程:

  1. 初始化$dp_{i,{i}}=0$,$dp_{i,S}=+\infty$,其中$i$是起点。
  2. 枚举所有的子集$S$,从小到大枚举所有的$i\in S$以及所有的$j\in S\setminus{i}$,计算$dp_{i,S}$。
  3. 最终的答案是$min_{i\in V} {dp_{i,V}}$,其中$V$表示所有点的集合。

下面是伪代码:

for s in subsets([1,2,...,n]):
    for i in s:
        if i==1 and s!={1}:
            continue # 起点只能在{s->{1}}这个子集中
        if i in s and i!=1:
            for j in s-{i}:
                dp[i][s] = min(dp[i][s], dp[j][s-{i}] + dist[j][i])
递归求解哈密顿路径

得到了$dp$数组之后,我们可以通过递归求解哈密顿路径。假设当前在点$i$,已经走过了点集$S$,我们需要选择一个点$j\not\in S$,从点$i$走到点$j$,并继续往下递归求解。

我们考虑用类似于前缀和的思想,求出$dp_{j,S\cup{j}}$,即从$j$这个点出发,经过集合$S\cup{j}$中所有点的最短路径。如果$dp_{j,S\cup{j}}$比当前的最优解还差,那么我们可以直接放弃这个分支,因为在这个分支上,我们无论如何也无法达到更优的解。

最后,当我们递归到达一个空集时,我们就找到了一条完整的哈密顿路径。如果此时路径的长度比我们的最优解还要小,那么我们就更新最优解。

下面是伪代码:

def hamilton_path(i, S, path, cost):
    if len(S)==n: # 已经走遍了所有点
        if cost<best_cost:
            best_cost = cost
            best_path = path
        return
    for j in range(1, n+1):
        if j not in S:
            new_cost = cost + dist[i][j]
            # 剪枝:如果new_cost达不到最优解,就不必走这个分支了
            if new_cost>=best_cost:
                continue 
            # 递归
            new_path = path + [j]
            hamilton_path(j, S|{j}, new_path, new_cost)
总结

通过动态规划的方法,我们可以在$O(n^2 2^n)$的时间内解决哈密顿路径问题。使用递归的方法可以在理论上达到最优解,但是实际上剪枝的效果可能会很大,因此最终的运行时间很大程度上取决于剪枝的效果。