Travelling Salesman Problem(TSP), 한국어로 외판원 문제라고 불린다고 합니다. 영문 위키백과에 따르면 TSP 는 다음과 같은 질문을 던지는 문제입니다.
"Given a list of cities and the distances between each pair of cities, what is the shortest possible route that visits each city exactly once and returns to the origin city?"
여러 도시들의 목록과 도시간의 거리 (혹은 도시들을 이어주는 도로의 길이) 가 같이 주어질 때, 모든 도시들을 정확히 한 번씩 방문하고 원래의 도시로 돌아오는 가장 짧은 루트는 무엇인가?
최단 거리를 묻기도 하고 꼭 시작하는 도시로 돌아오지 않아도 되는 등 약간의 변형이 있지만, 기본적으로 이와 같은 질문을 던지는 알고리즘 문제를 TSP 문제라고 합니다.
위키백과에 따르면 TSP 문제는 1930년 등장했고 최적화와 관련해서 가장 집중적으로 연구된 문제라고 합니다. 매우 어려운 문제이지만 많은 방법들이 연구되었기 때문에, 도시의 숫자가 백만개가 되어도 1% 정도를 제외한 거의 대부분이 완벽하게 해결된다고 하네요.
많은 뛰어난 분들이 이를 해결했다지만, 거인의 어깨에 올라타려면 최소한 풀이방법이라도 이해해야 하겠죠. 그래서 이 문제를 블로깅해보려고 합니다.
제가 생각하는 핵심적인 내용을 정리해보려고 합니다.
어떤 도시가 시작이 되어야 하는지 우리는 알 수 없으므로, 모든 도시에서 출발하는 경우의 수를 생각해보아야 한다.
모든 도시를 한 번만
방문해야 하므로 방문했음을 표시할 수 있는 무언가가 있어야 한다.
그리디 알고리즘처럼 가장 짧은 거리를 늘 선택하면 될 것이다. 모든 경우를 다 탐색해봐야 한다.
const aux = (lastVisited, isVisited, totalDist, visitedNum) => {
if (visitedNum === places.length) {
if(totalDist < curMinDist) {
curMinDist = totalDist;
return;
}
}
for(let i = 0; i < isVisited.length; i++) {
if (isVisited[i] === false) {
let dist = calculateDistance(lastVisited, places[i]);
isVisited[i] = true;
aux(places[i], isVisited, totalDist + dist, visitedNum + 1);
isVisited[i] = false;
}
};
}
전체 코드를 다 가져올 수는 없어서 제가 생각하는 핵심 코드만 추려봤습니다. 보시면 함수 내부에서 반복문이 돌아갈 때, 재귀적으로 동작하는 것을 알 수 있습니다. (주어진 레퍼런스 코드를 숙지하여 다시 풀어봤습니다.)
aux 함수의 2번째 인자에 들어가는 isVisited 는 방문한 도시인지를 boolean 값으로 파악하는 배열입니다. 도시의 갯수(places.length
) 만큼 boolean 값이 있는 1차원 배열인데요.
모든 경우를 다 탐색해야 하다보니, 매번 재귀적으로 동작하는 aux 함수의 앞뒤로, isVisited 의 i 번째 요소를 true로 바꾸었다가 다시 false 로 바꾸어주는 것을 알 수 있습니다.
알고리즘 블로깅을 해야겠다고 마음 먹는 경우는 대체로
이렇게 2 가지 경우입니다. 일반적으로는 블로깅을 할 엄두가 안 나는 알고리즘이 더 많거든요.
오늘은 다행히 이 2가지 경우에 잘 맞았던 것 같습니다. HA 가 끝나서 조금 더 여유가 있었던 탓일지도 모르겠지만 말이죠.