재귀 구조
본문기둥 세 개와 크기가 다른 원판들이 있다. 원판을 한 번에 하나씩 옮기되 큰 원판을 작은 원판 위에 올릴 수 없다는 규칙으로, 첫 기둥의 원판을 모두 셋째 기둥으로 옮겨야 한다. 전설에 따르면 어느 사원의 승려들이 원판 64개를 옮기고 있으며, 다 옮기는 날 세상이 끝난다고 한다. 원판 세 개는 몇 번 만에 옮길 수 있지만 64개는 왜 그렇게 오래 걸릴까? 이 문제를 프로그램으로 풀려면 어떻게 생각해야 할까?
1.2.1더 작은 같은 문제로 넘기기
원판 n개를 첫 기둥에서 셋째 기둥으로 옮기려면 가장 큰 원판이 셋째 기둥 맨 아래로 가야 한다. 그러려면 그 위의 원판 n − 1개를 먼저 둘째 기둥으로 치워야 한다. 그다음 가장 큰 원판을 셋째 기둥으로 옮기고, 둘째 기둥의 n − 1개를 셋째 기둥 위로 옮긴다. 원판 n − 1개를 옮기는 일은 원판 n개를 옮기는 일과 같은 모양의 더 작은 문제이다. 이렇게 자기 자신을 더 작은 입력으로 부르는 함수를 재귀 함수라 한다. 원판이 하나뿐이면 그냥 옮기면 되므로, 그때 더 부르지 않고 멈춘다. 이 멈추는 경우를 기저 사례라 한다.
원판 \(n\)개를 옮기는 최소 횟수 \(T(n)\)은 \(T(1) = 1\), \(T(n) = 2T(n-1) + 1\)을 만족하며, 이 점화식을 풀면 \[T(n) = 2^n - 1\]이다. 재귀 함수는 기저 사례가 있어야 하고, 호출할 때마다 문제가 기저 사례 쪽으로 작아져야 끝난다.
그림 1에서 원판 수를 바꾸어 보자. 원판 3개는 7번, 10개는 1023번, 20개는 약 105만 번이다. 1초에 한 번 옮기면 원판 20개는 약 12일이 걸린다. 원판 64개는 \(2^{64} - 1 \approx 1.8 \times 10^{19}\)번으로, 1초에 한 번씩 옮기면 약 5850억 년이 걸린다. 우주의 나이 138억 년의 40배가 넘는다. 원판이 하나 늘 때마다 일이 두 배가 되기 때문이다.1
1.2.2재귀로 생각하는 문제들
재귀는 문제의 구조가 자기 자신을 품고 있을 때 자연스럽다. 폴더 안에 폴더가 있는 파일 구조에서 모든 파일을 찾을 때, 수식 안에 괄호로 묶인 작은 수식이 있을 때, 리스트를 반으로 나누어 각각 정렬할 때가 그렇다. 재귀 함수를 짤 때는 세 가지를 확인한다. 기저 사례가 있는가, 재귀 호출마다 문제가 작아지는가, 작은 문제의 답으로 큰 문제의 답을 만들 수 있는가. 이 셋 가운데 하나라도 빠지면 함수가 끝나지 않거나 틀린 답을 낸다. 그리고 재귀 호출마다 스택 프레임이 쌓이므로, 너무 깊어지면 스택이 넘친다.
1.2.3예제 풀이
함수 하노이(n, 출발, 경유, 도착)를 재귀로 짰다. 하노이(3, "A", "B", "C")를 부르면 원판을 옮기는 출력이 몇 번 나오는가? 첫 세 번의 이동을 순서대로 쓰고, 이때 호출 스택이 가장 깊을 때 프레임은 몇 개인가?
\(T(3) = 2^3 - 1 = 7\)이므로 출력은 7번 나온다. 하노이(3)은 먼저 하노이(2, A, C, B)를, 그것은 하노이(1, A, B, C)를 부른다. 그래서 첫 이동은 A → C이다. 이어 원판 2를 A → B로 옮기고, 하노이(1, C, A, B)로 C → B를 옮긴다.
따라서 첫 세 번은 A → C, A → B, C → B로 작은 원판 둘이 B로 치워진다. 호출 스택은 하노이(3), 하노이(2), 하노이(1)이 쌓일 때 가장 깊어 3개이다. 원판이 n개이면 스택 깊이는 n이지만 이동 횟수는 \(2^n - 1\)이다. 즉 재귀 함수는 짧게 쓸 수 있어도, 그 일의 양은 호출이 만드는 나무의 크기만큼 늘어난다. 그래서 재귀 함수를 쓸 때는 호출이 몇 번 일어나는지 함께 따진다.
아래 탐구 카드에서 재귀 함수를 직접 짜 보고, 호출이 만드는 구조를 그려 보자.
- 하노이의 탑은 1883년 프랑스의 수학자 뤼카가 장난감으로 만들어 팔았고, 사원의 전설도 그때 함께 지어 붙인 이야기이다.
재귀 함수는 같은 모양의 더 작은 문제로 일을 넘기다가 기저 사례에서 멈추며, 작은 문제의 답으로 큰 문제의 답을 만든다. 하노이의 탑은 원판 n − 1개를 두 번 옮기고 가장 큰 원판을 한 번 옮기는 구조라 이동 횟수가 \(2^n - 1\)로 원판마다 두 배씩 늘어난다. 그래서 처음 질문의 원판 64개는 1초에 한 번 옮겨도 우주 나이의 40배가 넘게 걸려, 세상이 끝날 때까지 다 옮길 수 없다.