본문 바로가기

DEVELOP/알고리즘

6. BFS - Sorting Game(sortgame) 문제



문제를 해결하기 위해 배열이 가질 수 있는 각 상태를 정점으로 하는 그래프를 만들고,

해당 그래프에서 입력받은 상태 정점에서 정렬된 상태 정점까지 가는 최소 경로의 길이를 구할 수 있다.

즉, {3,4,1,2} 상태에서 3 4 를 뒤집으면 {4,3,1,2} 가 되므로, 두 정점을 간선으로 연결해주는 것이다.

그러면 bfs를 통해 최소 경로의 길이를 구할 수 있다.


그래프를 만드는 과정은 따로 구현하지 않고, bfs를 할 때 각 정점을 방문할 때마다 해당 정점의 인접한 정점,

즉 해당 상태에서 뒤집어서 만들수 있는 상태들을 계산하여 대기 큐에 넣도록 한다.


// Sorting Game 문제를 해결하는 bfs 알고리즘


int bfs(const vector<int>& perm){

int n = perm.size();


// 입력받은 배열의 정렬된 상태를 미리 계산하여 저장해둔다.

vector<int> sorted = perm;

sort(sorted.begin(), sorted.end());


queue< vector<int> > q;

map<vector<int>, int> distance;  // key값의 상태 정점까지 가는 최소 경로의 길이 = value 값


distance[perm] = 0;

q.push(perm);


while(!q.empty()){

vector<int> here = q.front();

q.pop();


// 현재 방문한 정점이 정렬된 목표 정점이면 종료한다.

if(here == sorted) return distance[here];


// 현재 방문한 정점까지 가는 최소 경로의 길이를 미리 저장해둔다.

int cost = distance[here];


for(int i = 0; i < n; i++){

for(int j = i + 2; j <= n; j++){

// i번째 숫자부터 j번째 숫자까지 배열을 뒤집는다.

reverse(here.begin() + i, here.begin() + j);


// 뒤집은 상태 정점이 아직 발견되지 않았다면(distance에 저장되지 않았다면) 대기 큐에 넣고 거리 계산한다.

if(distance.count(here) == 0){

q.push(here);

distance[here] = cost + 1;

}

reverse(here.begin() + i, here.begin() + j);  // 다시 원상태로 돌려놓는다.

}

}

}

// 오류 있을 경우

return -1;

}

 

위 코드는 문제를 해결할 수 있지만, 그 속도가 너무 느리다.

map에 접근하는데 드는 시간이 크기 때문이다.

따라서 다음 두 가지 속성을 이용하여 위 코드를 최적화 하도록 한다.


1. 배열의 각 숫자들이 값이 다르더라도, 상대적인 크기가 같은 배열들은 최소 경로의 길이가 같다.

예를 들어, {30, 40, 10, 20} 의 결과값은 {3, 4, 1, 2} 와 {45, 78, 22, 24} 의 결과값과 같다.

상대적인 크기를 비교했을때 정렬되어있는 순서가 같기 때문이다.


2. 이 문제에서 사용하는 그래프는 양방향 그래프이기 때문에, 

시작 정점에서 목표 정점까지의 최단 거리는 목표 정점에서 시작 정점까지의 최단 거리와 같다.


그러면 이 두 속성을 이용하여 크기 n의 배열에 대하여 

정렬된 배열 {0, 1, 2, ... , n} 정점에서 다른 모든 상태 정점으로 가는 최소 경로의 길이를 미리 계산해둘 수 있다.

그리고 입력받은 배열을 숫자들의 상대적 크기를 유지하면서 [0, n-1] 범위의 값으로 바꾸면 

이 상태 정점까지가는 최소 경로의 길이를 한번에 구할 수 있다.


map<vector<int>, int> toSort;  // 정렬된 상태에서 다른 모든 상태 정점까지 가는 최소 경로길이를 저장한다.


// 크기 n인 배열에 대하여 정렬된 상태 정점에서 다른 상태 정점까지 가는 최소 경로의 길이를 모두 계산한다.

void preCalc(int n){

vector<int> perm(n);

for(int i = 0; i < n; i++){

perm[i] = i;

}


queue< vector<int> > q;

toSort[perm] = 0;

q.push(perm);


while(!q.empty()){

vector<int> here = q.front();

q.pop();

int cost = toSort[here];


for(int i = 0; i < n; i++){

for(int j = i + 2; j <= n; j++){

reverse(here.begin() + i, here.begin() + j);


if(toSort.count(here) == 0){

toSort[here] = cost + 1;

q.push(here);

}

reverse(here.begin() + i, here.begin() + j);

}

}

}

}


int solve(const vector<int>& perm){

int n = perm.size();

vector<int> fixed(n);


for(int i = 0; i < n; i++){

int smaller = 0;

for(int j = 0; j < n; j++){

if(perm[i] > perm[j]) smaller++;

}

fixed[i] = smaller;

}


return toSort[fixed];

}


위 코드의 경우는 배열의 크기 n에 대해서 각 한번씩만 toSort[]를 계산하면 되므로 이전 코드보다 수십배 시간이 빨라지게 된다.

'DEVELOP > 알고리즘' 카테고리의 다른 글

6. BFS - 최단 경로 전략  (0) 2019.03.11
6. BFS - 어린이날(childrenday) 문제  (0) 2019.03.07
6. BFS  (0) 2019.02.28
5. DFS - 회의실 배정(meetingroom) 문제  (0) 2019.02.26
5. DFS - 감시 카메라 설치(gallery) 문제  (0) 2019.02.26