巡回セールスマン問題
概要
隣接行列で表された重み付き有向グラフおよび始点となる頂点番号 (start_vertex) を引数として受け取り, 以下の条件を満たす最短経路の距離を返す.
- 頂点 start_vertex から出発し, 出発点へ戻る閉路である
- 各頂点をちょうど1度だけ通る
また, そのような経路が存在しない場合は何かしら大きな値を返す. (C++ の場合は
計算量
頂点数を
Snippet
C++
cpp
long long traveling_salesman_problem(int start_vertex, std::vector<std::vector<long long>> &G) {
const long long LINF = 1000000000000000018;
const int n = G[0].size();
std::vector<std::vector<long long>> dp((1 << n), std::vector<long long>(n, LINF));
for (int v = 0; v < n; v++) {
if (v == start_vertex) {
continue;
}
dp[1 << v][v] = G[start_vertex][v];
}
for (int S = 0; S < (1 << n); S++) {
for (int v = 0; v < n; v++) {
if ((S >> v) & 1) {
continue;
}
for (int u = 0; u < n; u++) {
if (!((S >> u) & 1)) {
continue;
}
dp[S | (1 << v)][v] = std::min(dp[S | (1 << v)][v], dp[S][u] + G[u][v]);
}
}
}
return dp[(1 << n) - 1][start_vertex];
}
Python
python
def traveling_salesman_problem(start_vertex, graph):
n = len(graph)
dp = [[float("inf")] * n for _ in range(1 << n)]
for v in range(n):
if v == start_vertex:
continue
dp[1 << v][v] = graph[start_vertex][v]
for bit in range(1 << n):
for v in range(n):
if (bit >> v) & 1:
continue
for u in range(n):
if not ((bit >> u) & 1):
continue
dp[bit | (1 << v)][v] = min(dp[bit | (1 << v)][v],
dp[bit][u] + graph[u][v])
return dp[(1 << n) - 1][start_vertex]
解説
すでに訪れた頂点集合を
頂点
- dp[{v}][v] = dist[start_vertex][v] (start_vertex
v) - dp[S
{v}][v] = min(dp[S v][v], dp[S][u] + dist[u][v] | ) - 集合
に含まれていない頂点 を末尾に追加する - 追加の仕方は, 集合
に含まれている頂点 が末尾となるようなときを考えれば良い
- 集合
求める答えは全ての頂点をめぐり, start_vertex に戻ってくるときの辺の重みの総和の最小値なので以下のようになる.
- dp[(1<<n) - 1][start_vertex]