Hamiltonian paths哈密尔顿路径

A Hamiltonian path is a path that visits each node of the graph exactly once. For example, the graph contains a Hamiltonian path from node 1 to node 3:

image.png

If a Hamiltonian path begins and ends at the same node, it is called a Hamiltonian circuit. The graph above also has an Hamiltonian circuit that begins and ends at node 1:

image.png

Construction

Since there is no efficient way to check if a Hamiltonian path exists, it is clear that there is also no method to efficiently construct the path, because otherwise we could just try to construct the path and see whether it exists.

A simple way to search for a Hamiltonian path is to use a backtracking algorithm that goes through all possible ways to construct the path. The time complexity of such an algorithm is at least Hamiltonian paths哈密尔顿路径 - 图3, because there are Hamiltonian paths哈密尔顿路径 - 图4 different ways to choose the order of n nodes.(使用complete search + backtracking直接搜)

A more efficient solution is based on dynamic programming . The idea is to calculate values of a function possible (S, x), where S is a subset of nodes and x is one of the nodes. The function indicates whether there is a Hamiltonian path that visits the nodes of S and ends at node x. It is possible to implement this solution in Hamiltonian paths哈密尔顿路径 - 图5.(使用状压dp)

例题,最短Hamilton路径

  1. //求起点0到终点n-1的最短Hamilton路径
  2. //使用了状压dp
  3. #include <bits/stdc++.h>
  4. using namespace std;
  5. const int N = 20;
  6. int dp[1 << N][N], a[N][N], n;
  7. int main(){
  8. cin >> n;
  9. for (int i = 0; i < n; i++)
  10. for (int j = 0; j < n; j++)
  11. cin >> a[i][j];
  12. memset(dp, 0x3f, sizeof dp);
  13. dp[1][0] = 0; // 起点位置
  14. for (int mask = 1; mask < (1 << n); mask++)
  15. for (int end = 0; end < n; end++)
  16. if (mask >> end & 1) // 走过end
  17. for (int from = 0; from < n; from++){
  18. // hamilton路径要求,到达from结点的状态中,不能经过end这个结点,
  19. // 所以要把end这个点异或掉。从而,才能实现从from转移到end
  20. if ((mask ^ (1 << end)) >> from & 1)
  21. dp[mask][end] = min(dp[mask][end],
  22. dp[mask ^ (1 << end)][from] + a[from][end]);
  23. }
  24. cout << dp[(1 << n) - 1][n - 1] << '\n';
  25. return 0;
  26. }
  27. /*
  28. 填表法 :就是一般的动态规划,当前点的状态,可以直接用状态方程,根据之前点的状态推导出来。
  29. 刷表法:由当前点的状态,更新其他点的状态。需要注意:只用当每个状态所依赖的状态对它的影响相互独立。
  30. 上面那个是填表法
  31. 下面这个是刷表法
  32. */
  33. #include <bits/stdc++.h>
  34. using namespace std;
  35. const int N = 20;
  36. int dp[1 << N][N], a[N][N], n;
  37. int main(){
  38. cin >> n;
  39. for (int i = 0; i < n; i++)
  40. for (int j = 0; j < n; j++)
  41. cin >> a[i][j];
  42. memset(dp, 0x3f, sizeof dp);
  43. dp[1][0] = 0; // 起点位置
  44. for (int mask = 1; mask < (1 << n); mask++)
  45. for (int end = 0; end < n; end++)
  46. if (mask >> end & 1) // 走过end
  47. for (int nxt = 0; nxt < n; nxt++)
  48. if (!(mask >> nxt & 1))
  49. dp[mask | (1 << nxt)][nxt] = min(
  50. dp[mask | (1 << nxt)][nxt],
  51. dp[mask][end] + a[end][nxt]
  52. );
  53. cout << dp[(1 << n) - 1][n - 1] << '\n';
  54. return 0;
  55. }