본문으로 건너뛰기
김신건의 로그

외판원 순회 문제 (TSP)

· 수정 · 📖 약 2분 · 603자/단어 #algorithm #optimization #tsp #dp #bitmask
비트마스크, tsp, TSP, 외판원 순회, traveling salesman, traveling-salesman, bitmask dp tsp

정의

외판원 순회 문제 (TSP) 는 N 개 도시를 모두 정확히 한 번씩 방문하고 시작 도시로 돌아오는 최소 비용 경로 (Hamiltonian cycle) 를 찾는 NP-hard 문제.

Held-Karp 알고리즘 (bitmask DP) 으로 O(2^N · N^2) 에 해결. N ≤ 20 정도에서 사용.

문제 상황과 동기

모든 도시 방문 최소 비용 경로.

  • naive (brute-force): N! 개의 순열 전부 탐색. N=12 이면 479M, N=20 이면 불가능.
  • Held-Karp DP: 부분 경로를 비트마스크로 저장. 중복 계산 제거. O(2^N · N^2).

핵심 통찰: “어떤 도시들을 방문했고, 마지막으로 어디에 있는가” 만 기억하면, 방문 순서의 세부 사항은 무관. DP[mask][last] = 최소 비용.

시각화

핵심 아이디어

DP[mask][last] = "mask 에 속한 도시를 방문하고, 마지막이 last 일 때 최소 비용"

초기화:
    DP[1 << start][start] = 0

점화식:
    for each next not in mask:
        DP[mask | (1 << next)][next]
            = min(DP[mask | (1 << next)][next],
                  DP[mask][last] + cost[last][next])

정답:
    min over last of DP[(1<<N)-1][last] + cost[last][start]

구현

// TSP bitmask DP, O(2^N * N^2)
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;

int tsp(vector<vector<int>>& cost, int start) {
  int n = cost.size();
  int FULL = (1 << n) - 1;
  vector<vector<int>> dp(1 << n, vector<int>(n, INF));
  dp[1 << start][start] = 0;

  for (int mask = 1; mask <= FULL; mask++) {
      for (int last = 0; last < n; last++) {
          if (!(mask & (1 << last))) continue;
          if (dp[mask][last] == INF) continue;
          for (int nxt = 0; nxt < n; nxt++) {
              if (mask & (1 << nxt)) continue;
              int nmask = mask | (1 << nxt);
              dp[nmask][nxt] = min(dp[nmask][nxt],
                  dp[mask][last] + cost[last][nxt]);
          }
      }
  }

  int ans = INF;
  for (int last = 0; last < n; last++) {
      if (last == start) continue;
      ans = min(ans, dp[FULL][last] + cost[last][start]);
  }
  return ans;
}

int main() {
  vector<vector<int>> cost = {
      {0, 10, 15, 20},
      {10, 0, 35, 25},
      {15, 35, 0, 30},
      {20, 25, 30, 0}
  };
  cout << tsp(cost, 0);
  return 0;
}
stdin
4x4 대칭 행렬
결과
80

복잡도

항목
시간 (Held-Karp DP)O(2^N · N^2)
시간 (Brute-force)O(N!)
공간O(2^N · N)
N 제한N ≤ 20 (DP), N ≤ 1000 (heuristic)

변형 / 활용

  • DP with bitmask: N ≤ 20, 정확 최적해.
  • Branch and Bound: N=40~100, 정확 해, 가지치기 강력.
  • 2-opt / 3-opt: 휴리스틱, N=10^5 이상, 근사.
  • Christofides: 1.5-근사, metric TSP.
  • Concorde: TSP solver, N=10^4 이상 가능.
  • 차량 경로 문제 (VRP): TSP 일반화.

함정

1. DP 배열 크기

N=20 일 때 dp[2^20][20] = 약 20M int. 80MB. N=22 부터 약 400MB.

2. 경로 복원

점화식만으로 경로를 알 수 없음. 별도의 parent 배열 필요.

3. 대칭 vs 비대칭 TSP

cost[i][j] != cost[j][i] 면 비대칭. 점화식은 동일하나 triangle inequality 가 없으면 approximation 더 어려움.

4. overflow

C++ int 는 N=20, cost 10^6 이면 int overflow. long long 추천.

BOJ 연습 문제

번호제목정답률링크
BOJ 2098외판원 순회-kokoa-lab
BOJ 10971외판원 순회 2-kokoa-lab
BOJ 16198에너지 모으기-kokoa-lab
BOJ 16991외판원 순회 3 (2D)-kokoa-lab

참고

이 글의 용어 (3개)
배낭 문제 (Knapsack)algorithm
정의 배낭 문제 (Knapsack Problem) 은 무게 제한 W 인 배낭에 가치 vi, 무게 wi 인 물건 N 개 중 일부를 담아 총 가치 최대화하는 조합 최적화 문제. NP…
Branch and Bound: 분기 한정algorithm
정의 Branch and Bound (B&B) 는 완전 탐색을 하되, 상한/하한을 계산해 유망하지 않은 서브트리를 가지치기 하는 기법. 최적화 문제에 적용. 구성 요소 1. Br…
DP on Bitmask: 비트마스크 DPalgorithm
정의 부분집합 상태를 비트마스크로 인코딩 하여 DP 를 수행. 원소 수 N ≤ 20 정도에서 유효. 자세한 내용은 DP Bitfield 참조. 대표 예 TSP = 방문한 도시 m…

이 개념을 다룬 위키 페이지 (1)

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기