logseq 20260426

This commit is contained in:
songyc macbook 2026-04-26 22:32:53 +09:00
parent 62b36d7425
commit 367d5b15d3
2 changed files with 77 additions and 1 deletions

View File

@ -1 +1,2 @@
- [[배낭 문제 (Knapsack Problem)]] - [[배낭 문제 (Knapsack Problem)]]
- [[외판원 순환 문제 (Treveling Salesman Problem)]]

View File

@ -0,0 +1,75 @@
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 일 때, 남은 도시들을 모두 방문하고 출발지로 돌아가는 최소비용.
- 코드 #card
id:: 69ee100a-72a0-4698-8f71-539c0c28637d
- id:: 69ee108e-5aba-41db-97d4-8ad64ce1cd1a
```python
# 예시 입력: 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))
```