재귀 함수는 자기 자신을 호출하는 함수로, 문제를 더 작은 문제로 분할하여 해결하는 분할 정복 전략에 기반한다. 이 방식은 데이터 구조 탐색, 알고리즘 문제 해결 등 다양한 분야에서 활용된다.
재귀 함수가 실행될 때, 함수의 호출 정보는 Call Stack에 저장된다. Call Stack은 함수 호출 시 해당 함수의 매개변수, 지역 변수, 반환 주소를 저장하는 스택 자료구조이다. 재귀 함수의 실행 과정은 다음과 같다:
예를 들어, factorial(n)
함수는 n * factorial(n-1)
을 호출하며, factorial(1)
이 base case가 된다. 이 과정에서 Call Stack에는 factorial(n)
, factorial(n-1)
, ..., factorial(1)
의 정보가 차례로 쌓이고, 각 함수의 실행이 완료되면 결과를 반환하며 Call Stack에서 제거된다.
꼬리 호출 최적화는 함수의 반환값이 또 다른 함수 호출일 때, 새로운 스택 프레임을 생성하지 않고 기존의 스택 프레임을 재사용하여 재귀 함수를 최적화하는 방법이다. 이 최적화는 다음과 같은 조건에서 가능하다:
def fibonacci(n, a=0, b=1):
if n == 0:
return a
if n == 1:
return b
return fibonacci(n-1, b, a+b)
이 함수는 마지막에 자신을 호출하고, 추가 연산 없이 결과를 반환하기 때문에 꼬리 호출 최적화가 가능하다. 최적화를 지원하는 언어에서는 Call Stack을 재사용하여 큰 n 값에 대해서도 스택 오버플로 없이 함수를 실행할 수 있다.
모든 프로그래밍 언어가 꼬리 호출 최적화를 지원하는 것은 아니다. 예를 들어, Python과 Java는 기본적으로 TCO를 지원하지 않는 반면, Scala와 Erlang은 이를 지원한다. 재귀 함수를 사용할 때는 해당 언어의 스펙을 확인하고, 필요에 따라 재귀 대신 반복문으로 로직을 변경하는 등 최적화를 고려해야 한다.