재귀란?
재귀(recursion) 는 함수가 자기 자신을 부르는 기법입니다. 큰 문제를 같은
모양의 더 작은 문제로 쪼개어, 충분히 작아지면 직접 답하는 방식으로 풉니다.
재귀 함수에는 반드시 두 가지가 있어야 합니다.
- 기저 조건(base case): 더 쪼개지 않고 즉시 답하는 가장 작은 경우.
- 재귀 호출(recursive case): 더 작은 문제로 자신을 부르는 부분.
기저 조건이 없거나 잘못되면 무한히 자신을 불러 스택 오버플로가 납니다.
1. 가장 단순한 예: 팩토리얼
\(N! = N \times (N-1)!\)이고 \(0! = 1\)입니다. 이 정의가 그대로 코드가 됩니다.
long long fact(int n) {
if (n <= 1) return 1; // 기저 조건
return n * fact(n - 1); // 더 작은 문제
}
fact(4)는 4 * fact(3)을, 그것은 3 * fact(2)를... 부르며 내려가다가
fact(1)에서 멈추고, 그 값을 곱하며 거슬러 올라옵니다.
2. 호출 스택으로 이해하기
재귀는 호출 스택 위에서 동작합니다. fact(4)의 호출이 쌓였다가 풀리는 모습:
fact(4) 호출 → fact(3) 호출 → fact(2) 호출 → fact(1) = 1 반환
fact(2) = 2*1 = 2 반환 → fact(3) = 3*2 = 6 → fact(4) = 4*6 = 24
이 "내려갔다가 올라오는" 흐름을 머릿속에 그릴 수 있으면 재귀를 이해한 것입니다.
3. 재귀로 자연스러운 문제들
어떤 문제는 본질적으로 재귀적입니다.
| 문제 | 재귀 구조 |
|---|---|
| 피보나치 | \(F(n) = F(n-1) + F(n-2)\) |
| 하노이 탑 | \(N\)개 옮기기 = \(N{-}1\)개 옮기기 두 번 + 한 번 |
| 트리 순회 | 노드 처리 = 왼쪽 처리 + 오른쪽 처리 |
| 분할 정복 | 반으로 쪼개 각각 재귀 |
특히 트리와 그래프 탐색(DFS), 분할 정복, 백트래킹, DP가 모두
재귀 위에 세워집니다. 재귀는 Silver 이후 거의 모든 주제의 토대입니다.
4. 재귀와 반복의 관계
모든 재귀는 반복문(+스택)으로 바꿀 수 있고, 반대도 가능합니다. 단순 반복은
반복문이 빠르고 안전하지만, 분기가 여러 갈래인 탐색은 재귀로 짜는 것이
압도적으로 간결합니다. 상황에 맞게 고릅니다.
5. 위험: 깊이와 중복 계산
- 깊이 제한: 재귀가 너무 깊으면 스택이 넘칩니다. C++ 기본 스택은 보통 수십만
깊이, 파이썬은 기본 1000 깊이로 더 얕습니다. - 중복 계산: 단순 재귀 피보나치는 같은 값을 지수 번 다시 계산해
\(O(2^N)\)입니다. 메모이제이션(DP)으로 해결합니다 — 다음 주제로 이어집니다.
정리
재귀 = 기저 조건 + 자기 호출. "큰 문제를 같은 모양의 작은 문제로 쪼갤 수 있는가?"
를 물어보세요. 답이 예이면 재귀가 가장 자연스러운 도구입니다.