4.9 KiB
4.9 KiB
deck:: Logseq/coding tip
-
외판원 순환 문제 (Treveling Salesman Problem) 개요
- 정의: 여러 도시들이 있고 도시들 사이의 이동 비용이 주어졌을 때, 어느 한 도시에서 출발해서 {{c1 모든 도시를 딱 한 번씩만 방문하고 다시 출발 도시로 돌아오는}} 최소 비용을 구하는 문제. id:: bb52496e-ccb4-40fe-a1bc-20a77831c2d6
- 특징 : 출발 도시는 {{c1 아무 곳에서 시작해도 상관 없다.}} extra:: 어느 도시에서 출발하든 모든 곳을 한번씩 방문하고 출발도시로 돌아오는 경로가 존재한다면 사이클을 이루기 때문에 어느 곳에서 시작하든지 그 결과는 항상 동일하다. 따라서 보통 0번째 도시부터 시작하는 식으로 문제를 푼다. id:: 364f96a1-15dd-44e5-940e-ca7c5d27650f
- 시간 복잡도
- 완전 탐색을 수행 할 경우 모든 경우를 전부 다 계산해봐야 하기 때문에 도시의 개수가 늘어날 수록 기하급수적으로 탐색 횟수가 늘어난다.
- 완전 탐색 알고리즘 : {{c1 DFS, 순열}} 시간 복잡도 : {{c1 $O(N!)$}} extra:: N개의 도시를 순서를 고려해서 고르는 방법(순열)과 동일하므로 N!개의 경우가 존재한다. id:: 69ee0bf9-9b84-4c5d-8556-8532006bc7fc
- 따라서 문제를 최적화 하기 위해서 {{c1 비트 마스킹}}과 {{c1 다이나믹 프로그래밍}}을 사용하고, 이때 시간복잡도는 {{c1 $O(N^2 \times 2^N)$}}
extra:: dp 테이블은 도시숫자 * 비트마스킹 크기의 2차원 배열이므로 이것을 전부 다 도는데에 $N \times 2^N$이 소모된다. 또한 각 도시에 방문한 뒤 그 도시와 연결된 다음 도시를 탐색하므로 이것도
N만큼이 소비되므로 시간 복잡도는 이 두개를 곱한 것이 된다. id:: 69ee0c5e-9f3c-46b5-907a-ea1c2f856045
- 완전 탐색을 수행 할 경우 모든 경우를 전부 다 계산해봐야 하기 때문에 도시의 개수가 늘어날 수록 기하급수적으로 탐색 횟수가 늘어난다.
-
코드(python)
- 예시 문제 : 도시의 갯수 N, 비용 행렬 W가 주어졌을 때 모든 도시를 딱 한 번씩만 방문해서 다시 출발 도시로 돌아오는 최소 비용을 구하여라. 비용 행렬 W는
N \times N크기의 2차원 배열이고 W[i][j]의 의미는 i도시에서 j도시로 가는 비용이다. 또한 W[i][j]와 W[j][i]가 반드시 같지는 않으며, 값이 0인 경우 가는 길이 없다는 것을 의미한다.- 방문여부는 {{c1 비트마스킹}}을 통해 기록한다. extra:: 4개의 도시가 있고 1번과 3번 도시를 방문한 상태라면 1010, 0번 도시만 방문한 상태라면 0001 이런식으로 2진수로 표시한다. 즉 모든 방문 경우의 수는 {{c1 $2^N$}}개 이다. id:: 69ee0f02-5d43-43ae-b534-8674191ac58c
- dp 배열 정의
- dp[now][visited]의 뜻은? #card
id:: 69ee0fa3-05f4-4487-89b1-3188e26e8ed9
- 현재 now 도시에 있고, 지금까지 방문한 도시 상태가 visited 일 때, 남은 도시들을 모두 방문하고 출발지로 돌아가는 최소비용.
- dp[now][visited]의 뜻은? #card
id:: 69ee0fa3-05f4-4487-89b1-3188e26e8ed9
- 코드 #card
id:: 69ee100a-72a0-4698-8f71-539c0c28637d
- id:: 69ee108e-5aba-41db-97d4-8ad64ce1cd1a
# 예시 입력: N = 4, W = 비용 행렬 # 0 10 15 20 / 5 0 9 10 / 6 13 0 12 / 8 8 9 0 N = 4 W = [[0, 10, 15, 20], [5, 0, 9, 10], [6, 13, 0, 12], [8, 8, 9, 0]] INF = float("inf") # dp 배열: 현재 노드 N개 x 방문 상태 2^N개 # 초기값은 계산되지 않았음을 의미하는 None이나 -1로 설정 dp = [[None] * (1 << N) for _ in range(N)] def dfs(now, visited): if visited == (1 << N) - 1: # 현재 도시에서 출발지(0번)로 가는 길이 있다면 그 비용 반환, 없으면 무한대 반환 if W[now][0] != 0: return W[now][0] else: return INF # 2. 메모이제이션: 이미 계산된 상태라면 그대로 반환 if dp[now][visited] is not None: return dp[now][visited] # 3. 최소 비용 계산 min_cost = INF for next_city in range(N): # 다음 도시로 가는 길이 없고(0) 거나, 이미 방문한 도시(비트마스크 확인)라면 패스 if W[now][next_city] == 0 or (visited & (1 << next_city)) != 0: continue # 핵심 점화식: (now -> next_city 비용) + (next_city에서 남은 도시 방문하는 최소 비용) # 재귀를 통해 '남은 도시 방문 비용'을 가져오고, visited 상태에 next_city를 비트 OR 연산으로 추가! cost = W[now][next_city] + dfs(next_city, visited | (1 << next_city)) min_cost = min(min_cost, cost) dp[now][visited] = min_cost return min_cost # 0번 도시에서 출발, 방문 상태는 0번 비트만 켠 상태(1) print(dfs(0, 1))
- id:: 69ee108e-5aba-41db-97d4-8ad64ce1cd1a
- 예시 문제 : 도시의 갯수 N, 비용 행렬 W가 주어졌을 때 모든 도시를 딱 한 번씩만 방문해서 다시 출발 도시로 돌아오는 최소 비용을 구하여라. 비용 행렬 W는