Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

12-oesnuj #41

Merged
merged 2 commits into from
Sep 4, 2024
Merged

12-oesnuj #41

merged 2 commits into from
Sep 4, 2024

Conversation

oesnuj
Copy link
Member

@oesnuj oesnuj commented Jul 24, 2024

🔗 문제 링크

백준 | 하노이 탑

✔️ 소요된 시간

30분

✨ 수도 코드

1️⃣ 문제 선정 이유

DP를 도전하기 전에 먼저 재귀를 많이 풀면서 재귀적 사고를 키울려고 재귀 문제로 선택했습니다.
과거에 재귀를 처음 공부하다가 정말 힘들게 해결하고도 이해가 잘 안되었던 하노이 탑 이동 순서와 거의 똑같은 문제로 도전했습니다.
작년에 처음 하노이탑 도전했을 때는 혼자 짝수, 홀수 나누고... 뭐 풀면서 점점 산으로 갔었는데 이산수학 듣고 보니 재귀적 접근에 대한 시야가 달라진 느낌이네요.

2️⃣ 하노이탑 원판 이동 과정

하노이탑 해보기 링크

n개의 원판을 옮길때 반드시 거치는 과정

  1. n-1개의 원판을 임시 기둥에 옮긴다.
    image

  2. 현재 상황에서 가장 큰 원판을 목적지 기둥에 옮긴다.
    image

  3. n-1개의 원판을 목적지 기둥에 옮긴다.
    image

따라서 하노이탑의 원판 이동횟수를 f(n)이라고 두면 점화식 f(n) = 1 + 2f(n-1)으로 표현 할 수 있습니다.
1 : 가장 큰 원판으로 목적지 기둥으로 옮기는 횟수
2f(n-1) : n-1개의 원판을 임시 기둥으로 옮겼다가, 목적지 기둥으로 옮기는 횟수

다들 이산수학 수강하셨으니 아실텐데 이 점화식을 풀게되면
f(0)=0
image
원판 n개에 대한 하노이탑 원판 이동 횟수를 구할 수 있습니다.

3️⃣ 코드 설명

  1. 첫번째 출력 전체 원판 이동횟수는 2의 n제곱 -1을출력

  2. n <=20인 경우는 이동 과정까지 출력해주어야 하기 때문에 재귀함수 제작
    원판 갯수 n과 기둥번호 from, temp, to로 매개변수 입력으로 받아줍니다.
    탈출 조건 : n == 0이 될때 재귀함수 탈출

위에서 설명한 1번, 2번, 3번 과정에 맞게 재귀 함수의 매개변수를 적절히 바꿔서 호출합니다.
남은 1개의 원판을 목적지 기둥으로 옮기는 과정에서 실질적 이동이 발생하기 때문에 여기서 출발기둥, 목적기둥을 출력을 해줍니다.

이렇게 문제의 정의(원판 이동 과정)에 맞게 재귀함수를 만들어주면 컴퓨터가 알아서 깊게 들어가서 처리해줍니다!

void RecurMoveDisks(int n, int from, int temp, int to) {
    if (n == 0) return;
    //1. n-1개의 원판으로 temp로 옮긴다.
    RecurMoveDisks(n - 1, from, to, temp);

    //2. 남은 1개의 원판으로 to로 옮긴다. <- 이때 옮기는 과정 출력
    cout << from << " " << to << "\n";

    //3. temp에 있던 n-1개의 원판을 n-1개로 옮긴다.
    RecurMoveDisks(n - 1, temp, from, to);
}

📃 전체 코드

#include <iostream>
#include <string>
#include <cmath>
using namespace std;

void RecurMoveDisks(int n, int from, int temp, int to) {
    if (n == 0) return;
    //n-1개의 원판으로 temp로 옮긴다.
    RecurMoveDisks(n - 1, from, to, temp);

    //남은 1개의 원판으로 to로 옮긴다. <- 이때 옮기는 과정 출력
    cout << from << " " << to << "\n";

    //temp에 있던 n-1개의 원판을 n-1개로 옮긴다.
    RecurMoveDisks(n - 1, temp, from, to);
}

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    int n;
    cin >> n;

    //2의 n제곱 -1 출력하기
    string cnt = to_string(pow(2, n));
    int x = cnt.find('.');
    cnt = cnt.substr(0, x);
    cnt[cnt.length() - 1] -= 1;
    cout << cnt << "\n";

    //조건에 맞는 경우에만 재귀를 돌린다.
    if (n <= 20)  RecurMoveDisks(n, 1, 2, 3);
    return 0;
}

🙈트러블 슈팅

c++로 2의 n제곱 -1을 출력하는 과정에서 n의 최댓값이 100이었습니다..
2의 100제곱 - 1: 1,267,650,600,228,229,401,496,703,205,375
여기서 처음에는 long long 자료형으로 shift 연산을 사용해 2의 제곱을 나타냈는데 애초에 long long int로도 표현할 수 없더라구요..

long long moves = (1LL << n) - 1;
cout << moves << "\n";

그래서 계속 고민하다가 검색해서 이를 문자열로 표현하는 방법을 찾아냈습니다.

    string a = to_string(pow(2, n)); //제곱을 문자열로 만들고
    int x = a.find('.');  //pow는 무조건 double 형을 반환하기 때문에 소수점이 존재한다. 이 .의 인덱스를 찾는다.
    a = a.substr(0, x); //. 전까지 substring 수행
    a[a.length() - 1] -= 1; //만들어진 문자열의 마지막 숫자에 -1을 수행한다.

📚 새롭게 알게된 내용

  1. cpp pow 연산에서 정수형 범위를 벗어나는 수를 출력하는 방법

Copy link
Collaborator

@pu2rile pu2rile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제곱 계산을 위해 문자열을 사용하는 게 굉장히 인상적이네요!!
매개변수 4개를 사용하면서 temp라는 보조 기둥을 사용하는 것도 신기했습니다... 저는 오로지 문제 해결만을 위해서 기둥이 3개라는 전제 하에 (6 - start - end) 수식을 사용했거든요!
그리고 c++과 달리 파이썬은 큰 정수를 다룰 수 있어서 준서님처럼 따로 문자열 변환은 하지 않고 직접 2**n - 1을 계산했습니다 pr 수고하셨어요!

파이썬 코드
def hanoi_tower(n, start, end):
    if n == 1:
        print(start, end)
        return

    hanoi_tower(n-1, start, 6-start-end)
    print(start, end)
    hanoi_tower(n-1, 6-start-end, end)

n = int(input())
print(2**n - 1)

if n <= 20:
    hanoi_tower(n, 1, 3)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants