
이 글에서는 TSP가 무엇인지 알아보고, TSP를 해결하 방법중 가장 널리 알려진 DP로 해결하는 방법을 학습합니다.
ⓐ 0 → 1 → 2 → 3 → 4 → 0
ⓑ 0 → 1 → 2 → 4 → 3 → 0
...
ⓒ 0 → 2 → 3 → 4 → 1 → 0
...
ⓓ 0 → 3 → 1 → 2 → 4 → 0
...
다음은 위 4가지 경로에서 계산이 중복되는 경우입니다.
1. ⓐ, ⓑ 경로에서 0 → 1 → 2 경로를 구하는 과정이 중복됩니다.
2. ⓐ, ⓒ 경로에서 2 → 3 → 4 경로를 구하는 과정이 중복됩니다.
3. ⓑ, ⓓ 경로에서 1 → 2 → 4 경로를 구하는 과정이 중복됩니다.
TSP를 DP로 해결하기 위한 점화식은 다음과 같습니다.
(어떤 도시에서 시작해도 문제를 해결할 수 있으므로 문제를 단순화하기 위해 시작 도시를 0번 도시로 고정한 뒤 이를 기반으로 문제를 해결할 수 있도록 점화식을 설정합니다.)
0번 도시에서 아무 도시도 거치지 않고 번 도시로 가는 최소 비용은 직접적으로 0번 도시에서 번 도시로 가는 가중치와 같습니다. 만약 두 도시가 연결되어 있지 이동이 불가하다면 이 비용은 ∞로 설정됩니다.
에 속한 도시중 도시 하나를 선택한다. 이를 번 도시라 하자. 0번 도시에서 시작해 번 도시를 제외하고 에 속한 도시를 한 번씩 방문해 번 도시에 도달하는 최소비용을 구한다. 해당 비용에 번 도시에서 번 도시로 가는 비용을 구한다. 이 과정을 에 속한 모든 도시에 대해 반복한 뒤 최소값을 구하는 식입니다.
전체 도시의 개수가 N개라면 는 다음과 같이 나타낼 수 있습니다.
int[][] D = new int[N][(int) Math.pow(2, n - 1)]
D[i][A]를 구하는 함수는 다음과 같은 틀로 구현할 수 있습니다.
int getCost(int i, int A) {
	if (D[i][A] 값을 이미 구했다면) return D[i][A];
	D[i][A] 구해 값 저장하기
    
    return D[i][A];
}
백준에 부가적인 외판원 순회 알고리즘을 구현하는 문제가 있습니다. 해당 문제 풀이 코드를 통해 전체 TSP 알고리즘을 보여드리겠습니다.
링크: https://www.acmicpc.net/problem/2098



import java.io.*;
import java.util.*;
public class Main {
    static int N;
    static int Arange;  // 집합 A를 비트마스크로 표현했을 때 가질 수 있는 최대 값. 즉, 2^(N-1)
    static int[][] W;  // 인접 행렬
    static int[][] D;
    static final int INF = Integer.MAX_VALUE;  // infinite 를 대변 
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        N = Integer.parseInt(br.readLine());
        W = new int[N][N];
        for (int i = 0; i < N; ++i) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            
            for (int j = 0 ; j < N; ++j)
                W[i][j] = Integer.parseInt(st.nextToken());
        }
        // tsp[i][A] 0번 도시에서 A에 속한 도시를 거친 후 i번 도시로 오는 최소 비용
        Arange = (int) Math.pow(2, N - 1);
        D = new int[N][Arange];
        // 구하지 않은 값은 모두 -1 로 초기화
        for (int i = 0; i < N; ++i)
            for (int j = 0; j < Arange; ++j)
                D[i][j] = -1;
        // 초기값 설정
        D[0][0] = 0;
        for (int i = 1; i < N; ++i) {
            if (W[i][0] == 0) D[i][0] = Integer.MAX_VALUE;
            else D[i][0] = W[i][0];
        }
        System.out.println(tsp(0, Arange - 1));
    }
    public static int tsp(int i, int a) {
        
        // 이미 D[i][a] 값을 구했었다면, 해당 값을 반환
        if (D[i][a] != -1)
            return D[i][a];
        int minCost = INF;
        for (int j = 1; j < N; ++j) {
            // 1번 도시부터 (N-1)번 도시 중 a 에 속한 도시만 고려한다.
            if ((a & 1 << (j - 1)) == 0) continue;
            
            // prev = D[j][A - {j}]
            int prev = tsp(j, a & ~(1 << (j - 1)));
            
            // 갈 수 없는 루트인 경우 건너뛴다.
            if (prev == INF || W[i][j] == 0) continue;
            
            minCost = Math.min(minCost, W[i][j] + prev);
        }
        
        return D[i][a] = minCost;
    }
}
이번 포스팅에서는 TSP에 대해 알아보고, 특히 동적 계획법(DP)을 이용한 해결 방법에 초점을 맞춰 공부해 보았습니다. 사실, TSP와 같은 복잡한 최적화 문제는 직관적으로 접근하기 어렵고, 따로 공부하지 않는다면 해결 방법을 떠올리기 쉽지 않습니다.
하지만 한 번 시간을 들여 DP와 같은 알고리즘을 깊이 있게 이해하고, 실제 문제에 적용해 보면 이후 비슷한 유형의 문제를 훨씬 효과적으로 해결할 수 있는 기반을 마련할 수 있을 것입니다.