※ 다음에서 이어짐
하노이 탑(Tower of Hanoi)은 퍼즐의 일종입니다. 세 개의 기둥과 이 기동에 꽂을 수 있는 크기가 다양한 원판들이 있고, 퍼즐을 시작하기 전에는 한 기둥에 원판들이 작은 것이 위에 있도록 순서대로 쌓여 있습니다. 게임의 목적은 다음 두 가지 조건을 만족시키면서, 한 기둥에 꽂힌 원판들을 그 순서 그대로 다른 기둥으로 옮겨서 다시 쌓는 것입니다.
하노이 탑의 세 개의 기둥을 왼쪽 부터 1번, 2번, 3번이라고 하겠습니다. 1번에는 n개의 원판이 있고 이 n개의 원판을 3번 원판으로 최소 횟수로 옮기려고 합니다.
1번 기둥에 있는 원판의 개수 n이 매개변수로 주어질 때, n개의 원판을 3번 원판으로 최소로 옮기는 방법을 return하는 solution를 완성해주세요.
n | result |
---|---|
2 | [[1,2],[1,3],[2,3]] |
불과 몇 개월 전에는 알고리즘 조차 몰랐는데 지금 다시 보니 풀이 방식과 재귀 형태가 DFS와 유사하여 다시 풀게 됨.
하노이 탑의 기둥이 3개 있다고 가정할 때, 첫 번째 기둥에 쌓여있던 블록을 전부 원 상태로 세 번째 기둥으로 옮기는 것.
단, 블록은 자신보다 더 큰 블록을 위에 올릴 수 없다는 제한이 있음.
결론적으로 하노이 탑은 한꺼번에 해결할 수 없으며, 가장 크기가 큰, 맨 밑에 있던 블록부터 차례대로 세 번째 기둥에 쌓아야 함.
그러기 위해선 다음을 수행해야 함.
백준에서 풀었던 하노이 탑 문제는 바로 출력해주는 문제이지만, 이번 문제는 이를 배열에 담아서 출력해야 하므로 구조적으로 수정할 필요가 있음.
<전에 작성했던 hanoi 함수>
static void hanoi(int n, int from, int to) throws IOException {
if (n == 1) {
bw.write(from + " " + to + "\n");
return;
}
int empty = 6 - from - to;
hanoi(n-1, from, empty);
bw.write(from + " " + to + "\n");
hanoi(n-1, empty, to);
}
문제의 편리함을 위해 배열이 아닌 리스트를 사용해서 구할 예정임.
배열의 크기가 로 정해지긴 했는데 idx 변수를 새로 만들어서 카운팅하도록 했더니 런타임 에러가 떴음.
public void hanoi(int n, int from, int to) {
if (n == 1) {
list.add(new int[] {from, to});
return;
}
int empty = 6 - from - to;
hanoi(n-1, from, empty);
list.add(new int[] {from, to});
hanoi(n-1, empty, to);
}
1) depth
코드를 봤을 때 값을 바꿔가면서 재귀를 하는 점, 재귀를 1이라는 depth까지 진행한다는 점에서 DFS와 유사함.
여기서의 depth는 이동해야 할 블록의 개수를 칭함. 더 이해 쉽게 설명한다면 블록에 번호가 1번부터 n번까지 가벼운 순으로 적혀있으며, hanoi에서의 n은 n번 블록을 from 기둥에서 to 기둥까지 옮기는 과정을 뜻함.
2) 재귀 형식
hanoi(n-1)이 두 번 실행된다고 설명했는데 이는 위의 예시처럼 두 형제 노드가 생기는 형태와 같아짐.
여기에 형제 노드 첫 번째를 실행(출력)하고 난 후에 부모 노드도 실행(출력)하는 규칙이 있음.
이는 중위 순회와 비슷한 형태를 띄며, DFS에서도 다음과 같은 형태를 많이 사용함.
하노이에서의 중위 순회 형식은 첫 번째 형제 노드는 부모 노드를 옮기기 위한 사전 작업이라고 생각하면 됨. 더 자세히 풀면, n번 블록을 옮기기 위해 그 위에 쌓인 n-1개의 블록을 옮긴다고 생각하면 됨.
-> 4번 블록을 옮기기 위해 첫 번째 형제 노드에서 실행된 결과는 위에 쌓인 3개의 블록을 두 번째 기둥에 옮기는 작업이었음.
그랬기 때문에 4번 블록을 한 번 옮기고 나서는 옮길 일이 없던 것.
이 때문에 첫 번째 형제 노드를 실행하면 무조건 빈 기둥이 존재했던 것도 이 때문임.