컴퓨터/문제풀이집
재귀 함수의 기초적인 예제들을 다룰 때 하노이 탑 문제는 자주 등장합니다.
본문에서는 이 하노이탑 문제를 통해 재귀 함수를 이해해보도록 합시다.
하노이 탑이 어떻게 동작하는지에 대한 이해만 있다면 문제 자체는 어렵지 않을 수도 있습니다.
다만, 하노이 탑을 해결하는 문제가 어렵기 때문에 다소 복잡한 부분이 있습니다.
위의 문제를 보듯이 하노이 탑의 규칙을 수학적으로 풀이를 할 수 있으면 어려운 문제는 아닐 수 있습니다.
하노이 탑은 다음과 같은 전제조건이 있습니다.
이 부분을 수학적 풀이를 하기 전에, 눈으로 원판이 각각 1개 2개 3개일 경우를 살펴보도록 합시다.
원판 한 개일 때 경우를 눈으로 살펴보도록 합시다.
원판이 1개인 경우라면, 보조 탑(2번)을 사용하지 않고, 바로 목표 탑(3번)으로 이동할 수 있다는 것을 알 수 있습니다.
정리하자면,
시작 기둥의 원판이 1개라면 목표 탑으로 옮길 수 있다.
이제 이어서 원판이 2개인 경우를 살펴보도록 합시다.
Case 2를 통해 시작하는 탑에는 가장 큰 원판 1개만이 남아야 목표 탑에 옮길 수 있다는 것을 알았습니다.
따라서 가장 큰 원판은 2번 또는 3번 탑에 쌓을 옮겨야 됩니다. 다만, 하노이 타워의 규칙 원판은 자신보다 작은 원판 위에 올라올 수 없다는 규칙이 있습니다. 이 내용을 정리하자면,
시작 탑에는 옮겨야 하는 가장 큰 원판을 남긴다.
단 가장 큰 원판은 목표 탑에 가장 아래에 가야 되기 때문에
작은 원판은 2번(보조) 탑에 옮겨야 된다.
위의 case 1,2의 규칙을 이해하면서 원판이 3개인 경우를 살펴보도록 하겠습니다.
부분만을 바라보면 case1과 case 2의 과정을 반복하고 있습니다.
다만, 원판의 개수를 줄여가면서 동작을 수행하고 있을 뿐입니다.
이 부분들을 구체화하여 소스코드를 만들면 문제풀이가 가능해집니다.
간단하게 위의 내용을 재귀적 표현으로 구현해 봅시다.
결국 재귀적 함수 호출이 이루어지면서 시작 탑의 원판이 가장 큰 한 개가 남으면 목표 기둥으로 옮기면 됩니다.
즉, 원판의 개수 N에서 계속 -1개씩 줄어들어가며 원판이 1개가 되기 위해서 계속 시작 및 목표 탑을 설정하고 옮겨주면 됩니다.
#include <stdio.h>
int Count;
void solve_cnt(int cnt, int start, int end, int temp) {
//종료 조건 원판이 1개가 되면 옮길 수 있다.
if (cnt == 1) {
Count++;
return;
}
//재귀함수 호출 원판을 하나 빼고, 임시 기둥으로 옮김
solve_cnt(cnt - 1, start, temp, end);
//임시기둥으로 옮기기 위한 과정이 종료 후 시작점 부터 총착지 까지의 결과 값 출력
Count++;
//다시 재귀함수 호출 중간지점에 옮겨진 원판을 종착지 까지 이동하기 위한 호출
solve_cnt(cnt - 1, temp, end, start);
return;
}
void solve_pass(int cnt, int start, int end, int temp) {
//종료 조건 원판이 1개라면 시작점 부터 종착지 까지의 결과 값 출력
if (cnt == 1) {
printf("%d %d\n", start, end);
return;
}
//재귀함수 호출 원판을 하나 빼고, 임시 기둥으로 옮김
solve_pass(cnt - 1, start, temp, end);
//임시기둥으로 옮기기 위한 과정이 종료 후 시작점 부터 총착지 까지의 결과 값 출력
printf("%d %d\n", start, end);
//다시 재귀함수 호출 중간지점에 옮겨진 원판을 종착지 까지 이동하기 위한 호출
solve_pass(cnt - 1, temp, end, start);
return;
}
int main() {
//원판 갯수
int N;
scanf_s("%d", &N);
solve_cnt(N, 1, 3, 2);
printf("%d\n",Count);
solve_pass(N, 1, 3, 2);
return 0;
}
소스코드를 보면 옮기는 Count++ 또는 printf부분이 원판이 옮겨지는 과정입니다.
재귀적 순서가 동작하는 과정을 살펴보면 아래와 같습니다.
재귀 순서 (깊이) | 호출 중인 함수 | cnt | start | end | temp | |
1 | solve(3,1,3,2) | 3 | 1 | 3 | 2 | 3개의 원판을 1번에서 3번으로 옮겨라 |
2 | solve(2,1,2,3) | 2 | 1 | 2 | 3 | 2개의 원판을 1번에서 2번으로 옮겨라 |
3 | solve(1,1,3,2) | 1 | 1 | 3 | 2 | 1개의 원판을 1번에서 3번으로 옮겨라 종료조건 만족(옮김) (출력 값 : 1 -> 3) |
2 | solve(2,1,2,3) | 2 | 1 | 2 | 3 | 2개의 원판을 1번에서 2번으로 옮겨라2 첫번 째 재귀 종료 후 출력(옮김) ( 출력 값 1 -> 2) 두번 째 재귀 호출 |
3 | solve(1,3,2,1) | 1 | 3 | 2 | 1 | 1개의 원판을 3번에서 2번으로 옮겨라 종료조건 만족(옮김) (출력 값 : 3 -> 2) |
1 | solve(3,1,3,2) | 3 | 1 | 3 | 2 | 3개의 원판을 1번에서 3번으로 옮겨라2 첫번 째 재귀 종료 후 출력 ( 출력 값 : 1 -> 3) 두번 째 재귀 호출 |
2 | solve(2,2,3,1) | 2 | 2 | 3 | 1 | 2개의 원판을 2번에서 3번으로 옮겨라 |
3 | solve(1,2,1,3) | 1 | 2 | 1 | 3 | 1개의 원판을 2번에서 1번으로 옮겨라 종료조건 만족 (출력 값 : 2 -> 1) |
2 | solve(2,2,3,1) | 2 | 2 | 3 | 1 | 2개의 원판을 2번에서 3번으로 옮겨라2 첫번 째 재귀 종료 후 출력 ( 출력 값 2 -> 3) 두번 째 재귀 호출 |
3 | solve(1,1,3,2) | 1개의 원판을 1번에서 3번으로 옮겨라 종료조건 만족 ( 출력 값 : 1 -> 3) |
소스코드 동작을 살펴보면 재귀 호출의 깊이 따라 시작 탑과 목표 탑이 변경되면서 case 3과 같이 동일하게 동작합니다.
백준 17478 - 재귀함수가 뭔가요? (C언어 구현) (0) | 2022.05.31 |
---|---|
백준 2798 - 블랙잭 (0) | 2022.05.29 |
백준 2750 - 수 정렬하기 (0) | 2022.05.16 |
백준 2747 / 2748 - 피보나치 수 1,2 (재귀 vs 반복) (0) | 2021.11.25 |
백준 2503 - 숫자 야구(C언어) (0) | 2021.09.20 |
91년생 공학엔지니어의 개발일지
TODAY :
YESTER DAY :
TOTAL :
Commnet