외판원 문제는 모든 도시를 방문하는 어떤 경로를 찾나요?
외판원 문제는 한 출발점에서 모든 도시를 한 번씩 방문하고 돌아오는 경로 중 총비용이 가장 작은 것을 찾습니다. 한 목적지까지 가는 최단 경로와 목표가 다릅니다.
적용 조건·범위: 분석 조건: 1≤n≤9, 각 비용은 null 또는 0~1,000,000의 정수이며 방향 비용을 따로 봅니다.
읽기 전에 알아두면 좋아요
아래 개념이 익숙하면 본문을 바로 읽으세요. 낯선 개념만 링크에서 복습하면 됩니다.
- 재귀의 종료 조건: 작은 문제와 기저 조건으로 거듭제곱 계산하기재귀는 함수가 더 작은 문제로 자신을 다시 호출하는 방식입니다. 멈출 조건과 반환값을 확인합니다.
- 그래프 DFS: 방문 표시로 순환을 멈추고 연결을 따라가기깊이 우선 탐색(DFS)은 한 경로를 끝까지 따라간 뒤 돌아옵니다. 방문한 곳을 표시하는 방법을 확인합니다.
개념과 예제는 실행하지 않아도 읽을 수 있습니다. 내 컴퓨터·터미널에서 직접 실행하는 과정은 선택 실습입니다.
과정 위치와 선택 복습 안내
과정 위치: 유량·상태 압축과 계산 복잡도 (선택) · 알고리즘 개념
과정 위치는 이 글이 속한 묶음입니다. 이 단계의 모든 개념을 먼저 읽을 필요는 없습니다. 위에 연결한 개념 중 낯선 내용만 복습하세요. 문제의 조건·상태·갱신 순서는 익숙한 언어로 설명하면 됩니다.
JavaScript로 문법·실행 복습하기 (선택)
개념 글의 코드는 JavaScript 예시입니다. 문제 화면에서는 JavaScript·Python·Java·C++·C# 중 익숙한 언어의 함수를 작성하세요. 본문의 Node.js 입출력 예시는 로컬 실행을 위한 별도 안내입니다.

외판원 문제는 한 출발점에서 모든 도시를 한 번씩 방문하고 돌아오는 경로 중 총비용이 가장 작은 것을 찾습니다. 한 목적지까지 가는 최단 경로와 목표가 다릅니다.
선수 지식: 재귀·백트래킹의 되돌리기, 배열과 가중 그래프의 뜻을 알아야 합니다.
- 방문할 수 있는 다음 도시만 선택합니다.
- 마지막 복귀 간선까지 포함한 순환 비용을 비교합니다.
마지막 도시를 방문해도 아직 여행이 끝난 것은 아닙니다
도시 0, 1, 2가 있고 0→1이 2, 1→2가 3이라도 2→0이 없으면 이 순서는 완성된 순환이 아닙니다. 모든 도시를 방문했다는 조건과 출발점으로 돌아갈 수 있다는 조건을 함께 만족해야 합니다. 이 글은 null을 “간선 없음”으로, 숫자 0을 실제 무료 이동으로 사용합니다. 두 뜻을 같은 0에 넣지 않습니다.

| 상태 | 확인할 것 | 다음 행동 |
|---|---|---|
| 현재 경로 [0] | 방문하지 않은 이웃 | 1 또는 2로 분기 |
| 현재 경로 [0,1] | 1에서 2로 가는 간선 | 있으면 비용을 더해 진행 |
| 현재 경로 [0,1,2] | 2에서 0으로 복귀 가능 여부 | 가능할 때만 최소 비용 후보 |
예제 비용 행렬의 행은 출발 도시, 열은 도착 도시입니다. 반대 방향의 비용이 같을 필요는 없습니다. 무방향 여행을 다루려면 입력을 대칭으로 준비해야 합니다.
한 선택을 끝내면 방문 표시와 경로를 되돌립니다
next로 이동하기 직전에 visited[next]를 켜고 path에 추가합니다. 그 분기의 탐색이 끝나면 path에서 꺼내고 visited[next]를 끕니다. 다음 분기는 방금 선택하기 전과 같은 상태에서 시작해야 합니다. 방문 기록을 복구하지 않으면 다른 순서에서 사용할 도시까지 이미 방문한 것으로 남아 정답 후보가 사라집니다.

누적 비용은 함수 인자로 전달해 각 경로가 자기 값을 갖게 합니다. 완성된 순환의 최솟값은 호출 전체에서 공유해도 되지만, 아직 끝나지 않은 하위 문제의 최솟값을 전역 변수 하나로 돌려주면 서로 다른 상태의 답이 섞일 수 있습니다. 이 구현은 완성된 순환만 best와 비교하고 경로도 복사해 보관합니다.
작은 도시 수에서 정확성 기준으로 사용합니다
n개 도시에서 출발점을 고정해도 나머지 방문 순서가 빠르게 늘어납니다. 이 학습용 완전탐색은 n을 1~9로 제한합니다. 이는 9개까지 항상 어떤 기기에서 몇 초 안에 끝난다는 보장이 아니라 예제를 무리하게 확장하지 않기 위한 입력 계약입니다. 도시 하나만 있으면 이동 없이 비용 0으로 처리합니다.

순환이 없으면 cost는 Infinity, route는 빈 배열입니다. Infinity를 JSON으로 직렬화하면 null로 보일 수 있으므로 표시할 때 “경로 없음”으로 구분해야 합니다. 표준 예제의 출력은 실제 순환이 있는 경우이며, 테스트는 마지막 복귀 간선이 없는 경우도 별도로 확인합니다.
전체 코드와 직접 실행
개념을 읽었다면 아래 예제를 직접 실행하며 상태 변화를 확인할 수 있습니다.
전체 JavaScript 코드와 실행 방법 펼치기
별도 패키지는 필요하지 않습니다. Node.js가 처음이라면 브라우저와 Node.js 시작 안내부터 확인하세요.
tsp-backtracking.mjs 파일을 만들고 아래 전체 코드를 저장합니다.
function checkCosts(costs, start, limit) {
if (!Array.isArray(costs) || costs.length < 1 || costs.length > limit) {
throw new RangeError('허용 범위 안의 정사각 비용 행렬이 필요합니다.');
}
const n = costs.length;
if (!Number.isInteger(start) || start < 0 || start >= n) throw new RangeError('출발점 범위 오류');
for (const row of costs) {
if (!Array.isArray(row) || row.length !== n) throw new RangeError('정사각 행렬이 아닙니다.');
for (const value of row) {
if (value !== null && (!Number.isInteger(value) || value < 0 || value > 1000000)) {
throw new RangeError('비용은 null 또는 0 이상 1000000 이하의 정수입니다.');
}
}
}
}
function tspBrute(costs, start = 0) {
checkCosts(costs, start, 9);
const n = costs.length;
if (n === 1) return { cost: 0, route: [start, start] };
const visited = Array(n).fill(false);
const path = [start];
visited[start] = true;
let best = Infinity;
let bestRoute = [];
function search(city, accumulated) {
if (path.length === n) {
const back = costs[city][start];
if (back !== null && accumulated + back < best) {
best = accumulated + back;
bestRoute = [...path, start];
}
return;
}
for (let next = 0; next < n; next++) {
if (visited[next] || costs[city][next] === null) continue;
visited[next] = true; path.push(next);
search(next, accumulated + costs[city][next]);
path.pop(); visited[next] = false;
}
}
search(start, 0);
return { cost: best, route: bestRoute };
}
const costs = [[null, 2, 9, null], [1, null, 6, 4], [null, 7, null, 8], [6, 3, null, null]];
console.log(JSON.stringify(tspBrute(costs)));
파일이 있는 폴더의 터미널에서 실행합니다.
node tsp-backtracking.mjs
기대 출력
{"cost":21,"route":[0,2,3,1,0]}
비용과 적용 범위
시간: 출발점 고정 후 순열을 탐색합니다. 보수적으로 O(n·(n−1)!)로 상한을 설명합니다.

공간: 입력 행렬을 제외하고 방문·경로·재귀 스택 O(n)입니다.
분석 조건: 1≤n≤9, 각 비용은 null 또는 0~1,000,000의 정수이며 방향 비용을 따로 봅니다.
직접 확인해 보기
[[null,1],[null,null]]에서 출발점을 0으로 두면 비용 1의 순환이 있나요?

답과 이유 확인
없습니다. 0→1로 갈 수 있지만 1→0이 없으므로 완성된 순환이 아닙니다. Infinity와 빈 경로를 반환합니다.
함께 읽을 글
- 다익스트라의 원리 — 한 출발점의 최단 거리와 전체 도시 순환의 목표를 구분합니다.
- 완전탐색과 쌍 비교 — 정답 후보를 빠짐없이 열거하는 기준을 복습합니다.
참고 자료
- Competitive Programmer’s Handbook — 동적 계획법의 상태·전이, 부분집합과 순회 최적화의 일반 원리를 대조합니다. 예제와 설명은 이 원고의 독립 구현입니다.
이 글이 도움이 되었나요?
이어서 공부하기
체크포인트: 예제의 상태 변화를 손으로 추적하고 아래 문제에 적용하세요. 값이 없는 경우와 원소 하나인 경우도 설명해 보세요. 작은 기준 풀이와 비교하고 경로 복원·역방향 취소·입력 크기에 따른 비용을 설명하세요.
다음 추천 · 알고리즘 개념: 외판원 DP: 현재 도시와 방문 집합을 함께 저장하기
권장 순서는 학습 가이드와 같습니다. 다른 순서는 접힌 참고 목록에서 확인할 수 있습니다. 읽음 표시는 숙달이나 채점 결과가 아닙니다.
원래 과정 목록 순서 (선택)
읽음 기록
읽음 — / 전체 30개 · 필수 0개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.