외판원 순회 문제는 영어로 Traveling Salesman problem (TSP) 라고 불리는 문제로 computer science 분야에서 가장 중요하게 취급되는 문제 중 하나이다. 여러 가지 변종 문제가 있으나, 여기서는 가장 일반적인 형태의 문제를 살펴보자.
1번부터 N번까지 번호가 매겨져 있는 도시들이 있고, 도시들 사이에는 길이 있다. (길이 없을 수도 있다) 이제 한 외판원이 어느 한 도시에서 출발해 N개의 도시를 모두 거쳐 다시 원래의 도시로 돌아오는 순회 여행 경로를 계획하려고 한다. 단, 한 번 갔던 도시로는 다시 갈 수 없다. (맨 마지막에 여행을 출발했던 도시로 돌아오는 것은 예외) 이런 여행 경로는 여러 가지가 있을 수 있는데, 가장 적은 비용을 들이는 여행 계획을 세우고자 한다.
각 도시간에 이동하는데 드는 비용은 행렬 W[i][j]형태로 주어진다. W[i][j]는 도시 i에서 도시 j로 가기 위한 비용을 나타낸다. 비용은 대칭적이지 않다. 즉, W[i][j] 는 W[j][i]와 다를 수 있다. 모든 도시간의 비용은 양의 정수이다. W[i][i]는 항상 0이다. 경우에 따라서 도시 i에서 도시 j로 갈 수 없는 경우도 있으며 이럴 경우 W[i][j]=0이라고 하자.
N과 비용 행렬이 주어졌을 때, 가장 적은 비용을 들이는 외판원의 순회 여행 경로를 구하는 프로그램을 작성하시오.
입력
첫째 줄에 도시의 수 N이 주어진다. (2 ≤ N ≤ 10) 다음 N개의 줄에는 비용 행렬이 주어진다. 각 행렬의 성분은 1,000,000 이하의 양의 정수이며, 갈 수 없는 경우는 0이 주어진다. W[i][j]는 도시 i에서 j로 가기 위한 비용을 나타낸다.
항상 순회할 수 있는 경우만 입력으로 주어진다.
출력
첫째 줄에 외판원의 순회에 필요한 최소 비용을 출력한다.
유명한 문제이고 정석풀이는 비트마스킹을 이용하여 최적화를 해야하지만 이 문제에서는 시간제한이 널널하여 굳이 비트마스킹을 하지 않아도 해결할 수 있다.
각 도시를 순회하는 모든 경우를 확인하며 그때 가장 적게 드는 비용을 구하는 문제이다.
모든 경우를 구하기 위해 백트래킹을 이용하였다.
각 도시를 방문했다는 의미로 visited
배열을 사용하고, 각 경우의 비용을 저장할 cost
변수를 사용했다.
해당 도시를 방문하는 경우를 구하기 위해 visited[i] = true; cost += w[v][i];
를 각 해준 뒤, 해당 도시를 방문하지 않고 다음 도시를 방문하는 경우를 구하기 위해 해당 도시에 대한 DFS
호출이 끝난 후 cost -= w[v][i]; visited[i]= false;
로 원상복구 해준다.
모든 마을을 한번씩 방문했다면 마지막 마을에서 처음 마을로 돌아오는 비용을 더해주어야 한다.
모든 마을을 다 방문했는지에 대한 여부를 알기 위해 depth
매개변수를 이용했고, depth == n-1
이라면 모든 마을을 방문했음을 의미한다.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n;
int cost;
int totalCost;
vector<vector<int>> w;
vector<bool> visited;
void dfs(int v, int depth){
if (depth == n-1){
// 마지막에서도 처음으로 돌아가는 길이 있는지 확인해야한다.
if (w[v][0] == 0) return;
cost += w[v][0];
totalCost = min(cost, totalCost);
cost -= w[v][0];
}
for (int i=0; i<n; i++){
if (w[v][i] == 0 || visited[i]) continue;
visited[i] = true;
cost += w[v][i];
dfs(i, depth+1);
cost -= w[v][i];
visited[i] = false;
}
}
int main() {
cin.tie(0);
ios_base::sync_with_stdio(false);
cin >> n;
totalCost = 10000001;
cost = 0;
w.assign(n, vector<int>(n));
visited.assign(n, false);
for (int i=0; i<n; i++){
for (int j=0; j<n; j++){
cin >> w[i][j];
}
}
visited[0] = true;
dfs(0, 0);
cout << totalCost << endl;
return 0;
}
마지막에서 처음 마을로 돌아가는 부분에서 조금 해맸다.
우선 마지막 마을에서 처음마을로 돌아가는 길이 없다면 해당 경우는 불가능한 경우라 계산에서 제외시켰어야 했다.
또한 cost
변수를 매개변수로 넘기지 않고, 전역변수로 사용해서 각 경우마다 더하고 끝나면 값을 다시 빼야하는 번거로움이 생겼다.
이 때문에 마지막마을에서 처음 마을로 돌아가는 경우에서도 더하고 빼주는 작업을 잊어버리지 않고 했어야 했다.
만약 매개변수로 넘겨주었다면 덜 해맸을 것 같다.