问题描述
哈密尔顿路径属于旅行商问题,是一个NP完全问题,即没有一个合适的算法来解决它,只能用朴素算法(也就是通常所说的暴力算法)去进行优化。
但这类问题的时间复杂度是极其可怕的O(n!),当n比较小的时候,还可以通过暴力法解决,但当n=15的时候,15!=1307674368000;已经达到了万亿级别;你可以想象下就算以计算机的计算能力,也要跑多久才能解出答案;
所以NP问题最常用的解法是近似解,即无解,寻找大概率出现的近似解;
Hamilton路径的定义:在一张图中,从点0到点n-1不重不漏的恰好经过每一个点一次的路径。
问题分析
但哈密尔顿路径有一个特点:当你计算了其中i条路径的组合之后,计算i+1条的时候,是会重复计算前面已经计算过的值;
如何利用起来已经计算过的值,减少重复计算的步骤,这是一个优化算法的切入点;
因此状态机数据结构出来了;
我们知道有n条路径,那么每条路径都有两种状态:已经走过和没走过;那分别用0|1来表示;
那么当你处于某个路径的终点的时候,并走过了i条路径,你有多少种状态呢,还是一个阶乘i!,但是你可以发现有个规律,这些阶乘有共同重复的点;是不是可以用矩阵来表示呢,所以完整表示这些状态不仅需要2n次方的状态来表示路径是否走过,还需要一个二维深度去存储这些路径的组合状态,索性,我们只需要最短路径,但是怎么去表示呢?
试想下
前提条件:最短,所以我们处于某条路径上,前面走过的路径也肯定是那条路径为终点的最短路径;
目前我们已经走完了i条路径,但我们并不知道处于哪条路径的终点,那有多少情况呢,i种;
因为只有i条路径已经走过了啊;那么对于n条路径
,那么只需要n深度的矩阵就可以了;
所以我们需要一个二维的深度去表示每条路径;
那么状态机就是f[2次方][n];
那怎么把这个矩阵给计算出来呢?
解法分析
假设我们现在还是处于第i条路径上,并且走过了前i条;所以用顺序表示f的行为00001(i)11…111;
那么有几条路能够走到i呢,肯定只有前面已经状态为1的几条;
而状态机里存储的就是那条路径当时的最短路径,遍历这几条路径就可以得出此时这个状态下的最短路径;
那么
i >> k & 1表示路径已走过,且不是i的路径;
for(int k = 0; k < n; ++k) {
if(i >> k & 1) {
dp[i][j] = min(dp[i][j],
dp[i ^ 1 << j][k] + weight[k][j]);
}
}
当处于起点的时候,所有的路径都还没走过,所以第一行只有一个值,就是最开始起点的路径;
代码
for(let i = 1; i < i<<n; i++) {
for(let j = 0; j < n; j++) {
// 每一轮只求j
if(i & (1 << j)) {
for(let k = 0; k < n; k++) {
// j 不等于k, k已被开启、j未被开启的情况存在, k、j之间
if((i >> k & 1)) {
let value = dp[i - (1 << j)][k] + raod[k][j]
// 谁小我就从谁过来
if((dp[i][j] === -1) || (value < dp[i][j])) dp[i][j] = value
}
}
}
}
}