定义
哈密顿路径(Hamiltonian Path)
在图中找到一条路径,使得该路径恰好经过每个顶点一次。若路径的起点和终点相同,则称为哈密顿回路(Hamiltonian Cycle);若不同,则称为哈密顿通路。
与欧拉路径的区别
| 特征 | 欧拉路径 | Hamilton 路径 |
|---|---|---|
| 遍历对象 | 边(每条边恰好一次) | 顶点(每个顶点恰好一次) |
| 判定难度 | 多项式时间 | NP 完全 |
| 存在条件 | 度数奇偶性 + 连通性 | 无简单充要条件 |
NP 完全性
哈密顿路径问题是 NP 完全 的。这意味着:
- 目前没有已知的多项式时间算法来判定一般图中哈密顿路径的存在性
- 若存在多项式时间算法,则 (千禧年七大难题之一)
- 实际竞赛中,通常在特殊图或小规模情况下求解
判定条件
竞赛图(Tournament Graph)
定理:任意竞赛图必定存在哈密顿路径。1
证明(数学归纳法):
设 为竞赛图的顶点数。
-
基例:当 时,显然存在哈密顿路径。
-
归纳假设:假设对于 的竞赛图,命题成立。
-
归纳步骤:对于 的竞赛图:
- 任意删除一个顶点 ,得到 个顶点的子图
- 由归纳假设, 存在哈密顿路径
- 将顶点 插入路径 中:
- 找到第一个满足存在边 的顶点 (即 可以到达 )
- 若不存在,则将 放在路径末尾
- 否则,将 放在 和 之间
由此得到的路径正好经过所有 个顶点一次。
哈密顿回路的必要条件
Dirac 定理:设 为 的简单无向图,若对于任意顶点 ,都有 ,则 存在哈密顿回路。2
哈密顿回路的充分条件
Ore 定理:设 为 的简单无向图,若对于任意不相邻的顶点对 ,都有 ,则 存在哈密顿回路。
求解方法
状压 DP
对于带权完全图上的最短哈密顿路径问题(即经典 TSP),可以使用状压 DP 求解。
状态定义:
状态转移:
复杂度:
竞赛图哈密顿路径构造
竞赛图中构造哈密顿路径存在 的算法,其核心思想即上述归纳证明的构造性算法:
- 维护一条有序路径
- 依次插入新顶点 :在路径中找到第一个 使得存在边
- 若找到,将 插入 和 之间;否则放在末尾
代码模板
状压 DP(最短 Hamilton 路径)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<vector<int>> w(n, vector<int>(n));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> w[i][j];
}
}
const int INF = 1e9;
// dp[mask][i] = 经过mask中的点,终点为i的最短路径长度
vector<vector<int>> dp(1 << n, vector<int>(n, INF));
dp[1 << 0][0] = 0; // 起点为0
for (int mask = 1; mask < (1 << n); mask++) {
for (int i = 0; i < n; i++) {
if (!(mask & (1 << i))) continue; // i不在mask中
if (dp[mask][i] == INF) continue; // 不可达
for (int j = 0; j < n; j++) {
if (mask & (1 << j)) continue; // j已在路径中
int nmask = mask | (1 << j);
dp[nmask][j] = min(dp[nmask][j], dp[mask][i] + w[i][j]);
}
}
}
int ans = INF;
int fullMask = (1 << n) - 1;
for (int i = 1; i < n; i++) {
ans = min(ans, dp[fullMask][i] + w[i][0]); // 回到起点0
}
cout << ans << endl;
return 0;
}竞赛图 Hamilton 路径构造
#include <bits/stdc++.h>
using namespace std;
// 竞赛图:任意两点之间恰有一条有向边
// 返回哈密顿路径(顶点序列)
vector<int> tournamentHamiltonPath(const vector<vector<int>>& g) {
int n = g.size();
vector<int> path = {0}; // 初始路径只有一个顶点
for (int v = 1; v < n; v++) {
// 在路径中找到插入位置
int pos = path.size(); // 默认插到最后
for (int i = 0; i < (int)path.size(); i++) {
// g[v][path[i]] == 1 表示存在边 v -> path[i]
if (g[v][path[i]]) {
pos = i;
break;
}
}
path.insert(path.begin() + pos, v);
}
return path;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<vector<int>> g(n, vector<int>(n, 0));
// 读入竞赛图(每对顶点之间有一条有向边)
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j) continue;
int x;
cin >> x;
g[i][j] = x; // x为1表示 i -> j
}
}
vector<int> path = tournamentHamiltonPath(g);
cout << "Hamilton路径: ";
for (int v : path) cout << v << " ";
cout << endl;
return 0;
}例题:最短 Hamilton 路径
题目描述
给定 个城市的坐标 ,求从城市 出发,经过所有城市恰好一次后回到城市 的最短路径长度。3
解题思路
这是经典的 TSP 问题,使用状压 DP 求解。
设 表示从城市 出发,经过 中的城市(包含 ),当前在城市 的最短路径长度。转移时枚举下一个要访问的城市 。
答案为 。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<pair<int,int>> p(n);
for (int i = 0; i < n; i++) {
cin >> p[i].first >> p[i].second;
}
// 预处理距离矩阵
vector<vector<int>> w(n, vector<int>(n));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
long long dx = p[i].first - p[j].first;
long long dy = p[i].second - p[j].second;
w[i][j] = (int)(sqrt(dx*dx + dy*dy) + 0.5);
}
}
const int INF = 1e9;
vector<vector<int>> dp(1 << n, vector<int>(n, INF));
dp[1 << 0][0] = 0;
for (int mask = 1; mask < (1 << n); mask++) {
for (int i = 0; i < n; i++) {
if (!(mask & (1 << i))) continue;
if (dp[mask][i] == INF) continue;
for (int j = 0; j < n; j++) {
if (mask & (1 << j)) continue;
int nmask = mask | (1 << j);
dp[nmask][j] = min(dp[nmask][j], dp[mask][i] + w[i][j]);
}
}
}
int fullMask = (1 << n) - 1;
int ans = INF;
for (int i = 1; i < n; i++) {
ans = min(ans, dp[fullMask][i] + w[i][0]);
}
cout << ans << endl;
return 0;
}总结
| 方法 | 适用场景 | 时间复杂度 |
|---|---|---|
| 状压 DP | 带权完全图() | |
| 竞赛图构造 | 竞赛图(任意两点间恰有一条有向边) | |
| Dirac / Ore 定理 | 判定无向图哈密顿回路存在性 | 判定 |
哈密顿路径问题虽然本质上是 NP 难的,但在竞赛中往往通过挖掘题目图的特殊性质(如完全图、竞赛图、二分图等)来设计多项式算法。