WIPIVERSE

재귀 (컴퓨터 과학)

재귀(Recursion)는 컴퓨터 과학에서 함수나 절차가 자기 자신을 직접 혹은 간접적으로 호출하는 프로그래밍 기법 및 이론적 개념을 의미한다. 재귀 호출은 일반적으로 기저 조건(base case)과 재귀 단계(recursive step) 로 구성된다. 기저 조건은 재귀 호출을 종료시키는 조건이며, 재귀 단계는 문제를 더 작은 하위 문제로 분할하여 자기 자신을 호출한다.

주요 특징

  1. 자기 참조
    • 함수, 메서드, 프로시저가 자신의 정의 안에서 자신을 호출한다.
  2. 스택 프레임 사용
    • 각 재귀 호출은 호출 스택에 새로운 프레임을 추가한다. 기저 조건에 도달하여 반환될 때마다 스택 프레임이 차례로 해제된다.
  3. 종결성 보장
    • 모든 재귀 정의는 반드시 기저 조건을 포함해야 하며, 그렇지 않을 경우 무한 재귀가 발생하여 스택 오버플로우(Stack Overflow) 오류가 발생한다.

일반적인 활용 사례

분야 예시
정렬 알고리즘 퀵 정렬(QuickSort), 병합 정렬(MergeSort)
트리 탐색 전위(preorder), 중위(inorder), 후위(postorder) 순회
수학적 계산 피보나치 수열, 팩토리얼, 거듭제곱
탐색/백트래킹 미로 찾기, 퍼즐 풀이(예: N-Queens)
언어 파싱 재귀 하향 파서(Recursive‑descent parser)

구현상의 고려사항

  • 스택 깊이 제한: 대부분의 실행 환경은 호출 스택에 제한을 두고 있다. 깊이가 큰 재귀는 반복문으로 변환하거나 꼬리 재귀(tail recursion) 최적화를 활용한다.
  • 꼬리 재귀 최적화(TCO): 일부 컴파일러·인터프리터는 마지막 연산으로 자기 자신을 호출하는 꼬리 재귀를 반복문 형태로 변환하여 스택 사용을 최소화한다. 언어별 지원 여부가 다르다(예: Scheme, 일부 C 컴파일러, Python은 기본적으로 지원하지 않음).
  • 메모리 사용: 재귀 호출 시 각 프레임이 매개변수, 지역 변수, 반환 주소 등을 저장하므로 메모리 사용량이 증가한다. 메모리 효율이 중요한 경우 반복 구조로의 변환을 고려한다.

이론적 배경

  • 재귀 정의와 수학적 귀납법: 재귀 함수의 정확성 증명은 종종 수학적 귀납법을 사용한다. 기저 조건이 올바르게 동작함을 보이고, 재귀 단계가 기저 조건이 성립하는 경우에도 올바르게 동작함을 증명한다.
  • 데카르트의 자기참조 원리와 연결되는 논리적 개념으로, 컴퓨터 과학에서는 고정점 연산자(fixed‑point combinator)를 이용해 이름 없는 재귀 함수를 정의한다(예: 람다 계산에서 Y‑조합자).

언어별 재귀 지원 현황 (대표적인 예)

  • C / C++: 함수 내에서 자유롭게 재귀 호출 가능. 최적화 옵션에 따라 TCO가 적용될 수 있다.
  • Java: 메서드 재귀 호출 지원. JVM의 스택 크기 제한에 주의.
  • Python: 재귀 호출 가능하지만 기본 재귀 깊이 제한이 1000 수준이며, sys.setrecursionlimit() 로 조정 가능.
  • Haskell: 함수형 언어로, 꼬리 재귀 최적화가 자동으로 적용되는 경우가 많다.
  • Lisp 계열 (Scheme, Common Lisp): 재귀와 꼬리 재귀 최적화를 강조하는 설계 철학을 갖는다.

장점 및 단점

장점 단점
구현이 직관적이며 코드가 간결해질 수 있다. 호출 스택 사용으로 메모리 오버헤드가 발생할 수 있다.
복잡한 문제를 동일한 구조로 분할하여 해결한다. 기저 조건을 누락하거나 잘못 설계하면 무한 재귀가 발생한다.
수학적 귀납법을 통한 증명과 연계가 용이하다. 일부 환경에서는 성능 저하가 발생한다(특히 깊은 재귀).

결론

재귀는 알고리즘 설계와 구현에서 핵심적인 기법 중 하나이며, 적절히 사용될 경우 문제를 높은 수준의 추상화로 표현할 수 있다. 다만, 실행 환경의 스택 제한 및 성능 특성을 고려하여 필요에 따라 반복문으로 대체하거나 꼬리 재귀 최적화를 활용하는 것이 바람직하다.

둘러보기

더 찾아볼 만한 주제

    전체 문서 보기