最短Hamilton路徑
NP Hard問題,暴力時間複雜度$O(n*n!)$
這題正解其實是利用狀壓DP的方法來做,狀態轉移方程為
dp[i][j] = min{dp[i][j], dp[i - (1 << j)][k] + map[k][j]}
其中map陣列為權值,map[k][j]是點k到點j的權值
dp[i][j]表示當前已經走過點的集合為i,移動到j。所以這個狀態轉移方程就是找一箇中間點k,將已經走過點的集合i中去除掉j(表示j不在經過的點的集合中),然後再加上從k到j的權值
問題在於如何表達已經走過點的集合i,其實很簡單,假如走過0,1,4這三個點,我們用二進位制10011就可以表示,2,3沒走過所以是0
那麼走過點的集合i中去除掉點j也很容易表示 i - (1 << j)
,比方說i是{0,1,4},j是1,那麼 i = 10011
, (1 << j) = 10
, i - (1 << j) = 10001
那麼問題的答案就應該是dp[01....111][n-1],表示0~n-1都走過,且當前移動到n-1這個點
分析一下時間複雜度,n為20的時候,外層迴圈(1<<20),內層迴圈20,所以整體時間複雜度$O(20*2^{20})$,這比$O(n*n!)$快多了
import java.util.Scanner; public class Main { static int[][] dp = new int[1 << 20][20]; public static void main(String[] args) { Scanner cin = new Scanner(System.in); int n = cin.nextInt(); int[][] map = new int[n][n]; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) map[i][j] = cin.nextInt(); for (int i = 0; i < (1 << n); i++) for (int j = 0; j < n; j++) dp[i][j] = Integer.MAX_VALUE >> 1; dp[1][0] = 0; for (int i = 0; i < (1 << n); i++) // 二進位制列舉走過的點的集合 for(int j = 0; j < n; j++) if ((i >> j & 1) == 1) for (int k = 0; k < n; k++) if ((i - (1 << j) >> k & 1) == 1) dp[i][j] = Math.min(dp[i][j], dp[i - (1 << j)][k] + map[j][k]); System.out.println(dp[(1 << n) - 1][n - 1]); } }