[백준] 1914번 하노이 탑[c++]

백준 문제 링크 : 1914번: 하노이 탑 (acmicpc.net)

백준 1914번 하노이 탑 문제 사진

백준 1914번 문제 캡쳐

문제 요약

입력 값 : N( 1 <= N <= 100)

  1. 한 번에 한 개의 원판만을 다른 탑으로 옮길 수 있다.
  2. 쌓아 놓은 원판은 항상 위의 것이 아래의 것보다 작아야 한다.

문제 풀이 과정

오늘은 어제 갑자기 생각난 하노이 탑 문제를 풀어보려고 합니다. 예전에 프로그래밍을 배우고 얼마 되지 않았을 때 이 문제를 접하게 되었는데 그때는 아무리 생각해도 모르겠어서 넘어갔었는데 지금은 풀 수 있을 것 같아서 이렇게 풀어봤습니다.

하노이 탑 문제를 요약하면 크키가 큰 원판이 크기가 작은 원판 위에 올라가지 않도록 하면서 탑을 1번에서 3번 자리로 옮기는 문제입니다. 굉장히 유명한 문제이고 또 현실에서도 완구로 판매되고 있는 문제이기도 하죠.

이 문제를 해결하면서 이동 과정을 n의 값이 20 이하일 때만 출력하고 20 이상이면 이동 횟수만 출력하여야 합니다.

하노이 탑 직접 풀어보기

알고리즘을 확립하기 위해 직접 문제를 해결해보도록 합시다. 가장 쉬운 경우부터 살펴보기 위해 2층으로 먼저 풀어봅시다.

백준 하노이 탑 2층

위 영상을 보면 상단 층이 목표 공간과 현재 공간을 제외한 나머지 공간으로 이동 후 하단 층이 목표 공간으로 이동하고 기존에 있던 층을 목표 공간으로 이동 시키면서 탑이 이동 되는 것을 볼 수 있습니다.

이를 순서대로 나열해보면

  1. 가장 밑 층을 제외한 나머지를 옮기고자 하는 공간과 현재 탑이 있는 공간을 제외한 나머지 공간에 이동시킨다.
  2. 가장 밑 층을 옮기고자 하는 자리에 옮긴다.
  3. 나머지 층들을 옮기고자 하였던 부분으로 이동시킨다.

하노이 탑 문제에 층 수가 추가된다고 하더라도 위의 과정이 반복되면서 진행되게 되죠. 이를 확인하기 위해 아래 3층 또한 풀이해보았습니다.

만약 3개의 원판이 쌓여있는 상태에서 탑을 3번 자리로 옮기기 위해서는 아래와 같은 과정을 거쳐야 해요.

백준 하노이 탑 3층

위 과정을 목록으로 정리해보면

  • 3층 타워를 1->3으로 옮기고자 한다
  • 위 2개 층을 1(현재 장소) -> 2(목표 장소)으로 이동
    • 1번 층을 1(현재 장소) ->3(나머지 장소)으로 이동
    • 2번 층을 1(현재 장소) ->2(목표 장소)으로 이동
    • 1번 층을 1(나머지 장소) -> 2(목표 장소)으로 이동
  • 3번 층을 1(현재 장소) -> 3(목표 장소)로 이동
  • 옮겨둔 2개 층을 2(현재 장소) -> 3(목표 장소)으로 이동
    • 1번 층을 1(현재 장소) ->3(나머지 장소)으로 이동
    • 2번 층을 1(현재 장소) ->2(목표 장소)으로 이동
    • 1번 층을 1(나머지 장소) -> 2(목표 장소)으로 이동

이렇게 정리할 수 있습니다. 각 과정이 재귀적으로 3단계를 거쳐 진행됨을 알 수 있습니다. 때문에 이를 재귀 함수를 이용하여 풀면 아주 손쉽게 푸는 것이 가능합니다.

그럼 이제 재귀 함수에서 목표 공간과 현재 공간이 아닌 나머지 공간을 찾아내는 방법에 대해 알아보도록 합시다.

공간은 총 3개로 1에서 3으로 이동하기 위해선 2번 공간을 거쳐가야 합니다. 다른 경우들도 살펴보면 (2 ->3 = 1)(3 -> 1 = 2)(2->1 = 3) 등이 있죠 이곳에서도 규칙을 찾아볼 수 있습니다. 현재 공간 갯수인 3에 (from+to)를 3으로 나눈 나머지를 빼주면 나머지 공간이 나오게 되죠.

식을 만들면 (3 – (from + to) % 3)이 되며 이를 이용해 나머지 장소의 위치를 구할 수 있습니다. 이렇게 구한 위치로 탑을 옮기고 마지막 장소로 다시 옮겨가며 문제를 풀면 됩니다.

횟수의 크기 문제 해결

일단 이렇게 하노이 탑을 푸는 방법은 이제 알았습니다. 다만 이동 횟수를 출력하는 부분에서 또 다른 문제가 발생합니다. n은 최대 100으로 입력될 수 있는데 이 경우 하노이 탑 알고리즘을 그대로 이용하면서 이동 횟수를 계산이 너무 오래 걸리죠. 하지만 직접 하노이 탑 문제를 풀어보면서 이동 횟수를 구해보면 어떤 규칙을 찾아볼 수 있습니다.

[n = 1, result = 1] [n = 2, result = 3] [n = 3, result = 7]…

21 – 1 , 22 – 1, 23 – 1 …..

(0 * 2 + 1), ((0 * 2 + 1) * 2 + 1), ((0 * 2 + 1) * 2 + 1) * 2 + 1 …..

위와 같은 방식으로 횟수가 늘어나는 것을 확인할 수 있습니다. 이를 계산하여 먼저 연산 횟수를 출력하고 n의 크기가 20 이하인 요소들만 이동 과정을 출력하도록 만들어 주면 됩니다.

이때 주의해야 할 점은 숫자 크기가 정수형의 최대 크기를 벗어나기 때문에 배열로 자리 당 값을 저장하여 출력하는 것이 좋습니다.

코드는 아래와 같습니다.

#include <iostream>
#include <vector>
using namespace std;
//이동 처리
void move_a2b(short a,short b){
    cout << a << " " << b << "\n";
}
//하노이 타워 이동 처리
void hanoi_tower(short from,short to,short count){
    if(count == 0) return;
    hanoi_tower(from, 3 - (from + to ) % 3,count-1);
    move_a2b(from,to);
    hanoi_tower(3 - (from + to ) % 3,to,count - 1);

}
int main()
{
    ios::sync_with_stdio(false);
    int n;
    cin >> n;
    //이동 횟수 계산
    //크기가 매우 커서 long 형에 담을 수 없음
    vector<long> results;
    results.push_back(0);
    for (int i = 0; i < n; i++) {
        //값 연산
        for (int j = 0; j < results.size();j++) {
            results[j] *= 2;
            if (j == 0) {
                results[j] +=1;
            }
        }
        //10의 자리수가 넘어가면 10의 자리를 다음 자리로 더해줌
        for (int j = 0; j < results.size();j++) {
            int next = results[j] / 10;
            results[j] %= 10;
            if (next <= 0) continue;
            if (results.size() <= j + 1) {
                results.push_back(next);
            }
            else {
                results[j+1] += next;
            }
        }
    }
    //비용 출력
    for (int i = results.size() - 1; i >= 0 ;i--) {
        cout << results[i];
    }
    cout << "\n";
    if(n > 20){
        return 0;
    }
    //하노이 타워 출력
    hanoi_tower(1, 3,n);
    return 0;
}

다른 알고리즘 문제는 아래 링크에서 풀어보실 수 있습니다.

알고리즘 – PostiveGround

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤