From 367d5b15d36da7489cb43119fd6c6dd5b8a7b24d Mon Sep 17 00:00:00 2001 From: songyc macbook Date: Sun, 26 Apr 2026 22:32:53 +0900 Subject: [PATCH] logseq 20260426 --- pages/동적계획법(DP) (Dynamic Programming.md | 3 +- ...원 순환 문제 (Treveling Salesman Problem).md | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 pages/외판원 순환 문제 (Treveling Salesman Problem).md diff --git a/pages/동적계획법(DP) (Dynamic Programming.md b/pages/동적계획법(DP) (Dynamic Programming.md index bc06c59..104b009 100644 --- a/pages/동적계획법(DP) (Dynamic Programming.md +++ b/pages/동적계획법(DP) (Dynamic Programming.md @@ -1 +1,2 @@ -- [[배낭 문제 (Knapsack Problem)]] \ No newline at end of file +- [[배낭 문제 (Knapsack Problem)]] +- [[외판원 순환 문제 (Treveling Salesman Problem)]] \ No newline at end of file diff --git a/pages/외판원 순환 문제 (Treveling Salesman Problem).md b/pages/외판원 순환 문제 (Treveling Salesman Problem).md new file mode 100644 index 0000000..2e0c297 --- /dev/null +++ b/pages/외판원 순환 문제 (Treveling Salesman Problem).md @@ -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)) + ``` \ No newline at end of file